Search code examples
pythonpytestgenerated-codefixture

py.test: How to generate tests from fixtures


I'm writing a set of tools to test the behavior of a custom HTTP server: whether it is setting appropriate response codes, header fields etc. I'm using pytest to write tests.

The goal is to make requests to several resources, and then evaluate the response in multiple tests: each test should test a single aspect of the HTTP response. However, not every response is tested with every test and vice-versa.

To avoid sending the same HTTP request multiple time and reuse HTTP responses messages, I'm thinking of using pytest's fixtures, and to run the same tests on different HTTP responses I'd like to use pytest's generate test capabilities. import pytest import requests

def pytest_generate_tests(metafunc):
    funcarglist = metafunc.cls.params[metafunc.function.__name__]
    argnames = sorted(funcarglist[0])
    metafunc.parametrize(argnames, [[funcargs[name] for name in argnames]
                                    for funcargs in funcarglist])


class TestHTTP(object):
    @pytest.fixture(scope="class")
    def get_root(self, request):
        return requests.get("http://test.com")

    @pytest.fixture(scope="class")
    def get_missing(self, request):
        return requests.get("http://test.com/not-there")

    def test_status_code(self, response, code):
        assert response.status_code == code

    def test_header_value(self, response, field, value):
        assert response.headers[field] == value

    params = {
        'test_status_code': [dict(response=get_root, code=200),
                             dict(response=get_missing, code=404), ],
        'test_header_value': [dict(response=get_root, field="content-type", value="text/html"),
                              dict(response=get_missing, field="content-type", value="text/html"), ],
    }

The problem appears to be in defining params: dict(response=get_root, code=200) and similar definitions do not realize, I'd like to bind on the fixture and on on the actual function reference.

When running tests, I get this kinds of errors:

________________________________________________ TestHTTP.test_header_value[content-type-response0-text/html] _________________________________________________

self = <ev-question.TestHTTP object at 0x7fec8ce33d30>, response = <function TestHTTP.get_root at 0x7fec8ce8aa60>, field = 'content-type', value = 'text/html'

    def test_header_value(self, response, field, value):
>       assert response.headers[field] == value
E       AttributeError: 'function' object has no attribute 'headers'

test_server.py:32: AttributeError

How may I convince the pytest to take the fixture value instead of the function?


Solution

  • No need to generate tests from fixtues, just parameterize your fixture and write regular tests for the values it returns:

    import pytest
    import requests
    
    
    should_work = [
        {
            "url": "http://test.com",
            "code": 200,
            "fields": {"content-type": "text/html"}
        },
    ]
    
    should_fail = [
        {
            "url": "http://test.com/not-there",
            "code": 404,
            "fields": {"content-type": "text/html"}
        },
    ]
    
    should_all = should_work + should_fail
    
    
    def response(request):
        retval = dict(request.param)  # {"url": ..., "code": ... }
        retval['response'] = requests.get(request.param['url'])
        return retval  # {"reponse": ..., "url": ..., "code": ... }
    
    
    # One fixture for working requests
    response_work = pytest.fixture(scope="module", params=should_work)(response)
    # One fixture for failing requests
    response_fail = pytest.fixture(scope="module", params=should_fail)(response)
    # One fixture for all requests
    response_all = pytest.fixture(scope="module", params=should_all)(response)
    
    
    # This test only requests failing fixture data
    def test_status_code(response_fail):
        assert response_fail['response'].status_code == response_fail['code']
    
    
    # This test all requests fixture data
    @pytest.mark.parametrize("field", ["content-type"])
    def test_header_content_type(response_all, field):
        assert response_all['response'].headers[field] == response_all['fields'][field]