How can I provide a non-fixture pytest parameter via a custom decorator?

Question:

We have unit tests running via Pytest, which use a custom decorator to start up a context-managed mock echo server before each test, and provide its address to the test as an extra parameter. This works on Python 2.

However, if we try to run them on Python 3, then Pytest complains that it can’t find a fixture matching the name of the extra parameter, and the tests fail.

Our tests look similar to this:

@with_mock_url('?status=404&content=test&content-type=csv')
def test_file_not_found(self, url):
    res_id = self._test_resource(url)['id']
    result = update_resource(None, res_id)
    assert not result, result
    self.assert_archival_error('Server reported status error: 404 Not Found', res_id)

With a decorator function like this:

from functools import wraps

def with_mock_url(url=''):
    """
    Start a MockEchoTestServer and call the decorated function with the server's address prepended to ``url``.
    """
    def decorator(func):
        @wraps(func)
        def decorated(*args, **kwargs):
             with MockEchoTestServer().serve() as serveraddr:
                 return func(*(args + ('%s/%s' % (serveraddr, url),)), **kwargs)
        return decorated
    return decorator

On Python 2 this works; the mock server starts, the test gets a URL similar to "http://localhost:1234/?status=404&content=test&content-type=csv", and then the mock is shut down afterward.

On Python 3, however, we get an error, "fixture ‘url’ not found".

Is there perhaps a way to tell Python, "This parameter is supplied from elsewhere and doesn’t need a fixture"? Or is there, perhaps, an easy way to turn this into a fixture?

Asked By: ThrawnCA

||

Answers:

You can use url as args parameter

@with_mock_url('?status=404&content=test&content-type=csv')
def test_file_not_found(self, *url):
    url[0] # the test url
Answered By: Guy

Looks like Pytest is content to ignore it if I add a default value for the injected parameter, to make it non-mandatory:

@with_mock_url('?status=404&content=test&content-type=csv')
def test_file_not_found(self, url=None):

The decorator can then inject the value as intended.

Answered By: ThrawnCA

consider separating the address from the service of the url. Using marks and changing fixture behavior based on the presence of said marks is clear enough. Mock should not really involve any communication, but if you must start some service, then make it separate from

with_mock_url = pytest.mark.mock_url('http://www.darknet.go')


@pytest.fixture
def url(request):
    marker = request.get_closest_marker('mock_url')
    if marker:
        earl = marker.args[0] if args else marker.kwargs['fake']
        if earl:
            return earl

    try:
        #
        earl = request.param
    except AttributeError:
        earl = None


    return earl

@fixture
def server(request):
    marker = request.get_closest_marker('mock_url')
    if marker:
        # start fake_server


@with_mock_url
def test_resolve(url, server):
    server.request(url)
    
Answered By: msudder