Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

PyTest fails on a directory with multiple test files when modularizing fixtures

I'm using Py.Test to test functions in a Python Flask app.

I have the tests passing fine when I use one "app_test.py" file that contains all the fixtures and tests. Now that I've split the fixtures into their own module, and separated the tests into different modules which each import the fixtures module I'm running into issues.

If I run tests on each module individually, everything passes fine: pytest tests/test_1.py, pytest tests/test_2.py, pytest tests/test_3.py, etc. However trouble starts if I want to run all the tests in sequence with one command, e.g. pytest tests.

I get the first module of tests passing and all future ones report an error:

AssertionError: A setup function was called after the first request was handled.  
This usually indicates a bug in the application where a module was not imported 
and decorators or other functionality was called too late.
E  To fix this make sure to import all your view modules, database models 
and everything related at a central place before the application starts 
serving requests.

All the tests and fixtures in one file looks something like this:

# app_test.py

from flask_app import create_app
@pytest.fixtures(scope="session")
def app(request):
    app = create_app()
    with app.app_context():
        yield app

@pytest.fixture(scope="session"):
def client(request, app):
    client = app.test_client()
    return client


def test1(client):
    # test body

def test2(client):
    # test body

...

I run $ pytest app_test.py and everything runs perfectly.

Now let's say we split those into three different modules: fixures.py, test_1.py and test_2.py. The code now looks like this.

# tests/fixtures.py
from flask_app import create_app
@pytest.fixtures(scope="session")
def app(request):
    app = create_app()
    with app.app_context():
        yield app

@pytest.fixture(scope="session"):
def client(request, app):
    client = app.test_client()
    return client

# tests/test_1.py
from tests.fixtures import app, client
def test_1(client):
    # test body

# tests/test_2.py
from tests.fixtures import app, client
def test_2(client):
    # test body

If we run $ pytest tests then tests/test_1.py will pass, and tests/test_2.py will raise the error.

I've looked at this gist, and have tried tagging the test functions with @pytest.mark.usefixture with no success.

How can one run Py.Test with modularized fixtures on a directory that contains multiple test files?

like image 479
ABM Avatar asked Oct 17 '17 14:10

ABM


2 Answers

You use the fixtures slightly improperly.

Specifically, you declare multiple fixtures of the same name and with the same function-object, but detected at different modules. From the pytest point of view, these should be the separate functions, because pytest does dir(module) to detect the tests & fixtures in the file. But somehow due to the same function-object, I think, pytest remembers them as the same; so once you get to the second test file, it tries the locally detected fixture name, but finds it already prepared and fails.

The proper use would be to create a pseudo-plugin conftest.py, and put all the fixtures there. Once declared, these fixtures will be available to all files in that dir and all sub-dirs.

Note: such fixtures must NOT be imported into the test files. The fixtures are NOT the functions. Pytest automatically prepares them and feeds them to the tests.

# tests/conftest.py
import pytest
from flask_app import create_app

@pytest.fixtures(scope="session")
def app(request):
    app = create_app()
    with app.app_context():
        yield app

@pytest.fixture(scope="session")
def client(request, app):
    client = app.test_client()
    return client

And the test files (note the absence of the imports!):

# tests/test_1.py
def test_1(client):
    # test body

# tests/test_2.py
def test_2(client):
    # test body

See more: https://docs.pytest.org/en/latest/writing_plugins.html

like image 96
Sergey Vasilyev Avatar answered Oct 05 '22 14:10

Sergey Vasilyev


If you are using an application instance then this will work for you:

import os
import tempfile
import pytest
from my_app import app, db

@pytest.fixture
def client():
    db_fd, app.config["DATABASE"] = tempfile.mkstemp()
    app.config["TESTING"] = True

    with app.app_context():
        db.init_app(app)

    yield app

    os.close(db_fd)
    os.unlink(app.config["DATABASE"])

@pytest.fixture
def client(request):
    client = app.test_client()
    return client
like image 34
Joe Gasewicz Avatar answered Oct 05 '22 15:10

Joe Gasewicz