DV82XL
DV82XL

Reputation: 6639

Add PyTest fixtures to a test class using dependency injection

I'm using Python 3 with PyTest and have defined a number of fixture objects in conftest.py. The problem I'm having is that there are some fixture objects that will be needed by every test case. Requesting these fixture references in all test cases results in a lot of repeated boilerplate code.

Here's the fixture in conftest.py:

def fixtureA(request):
    _fixture = FixtureA()
    # initialize the fixture, do stuff with request
    return _fixture

Here's the current test class, where I want to remove all fixtures from arguments for readability:

class TestSomeFeature(BaseTest):

    def test_some_function(self, fixtureA, fixtureB, fixtureC):
        fixtureA.doSomething()
        # execute test case, do stuff with other fixtures     

    def test_some_other_function(self, fixtureA, fixtureB, fixtureC):
        data = fixtureB.getData()
        # execute test case

This approach works, but I'd like to find a way to use dependency injection (or similar) to automagically inject the fixtures in BaseTest attributes without having to specify them in every test case's argument list. I'm looking for something like this, but open to any other suggestions:

class BaseTest:
    # This does not work, since pytest does not allow constructors in the test class
    def __init__(fixtureA, fixtureB, fixtureC):
        self.fixtureA = fixtureA
        self.fixtureB = fixtureB
        self.fixtureC = fixtureC

I want the test class to look like this, much cleaner!

class TestSomeFeature(BaseTest):

    def test_some_function(self):
        self.FixtureA.doSomething()
        # execute test case

    def test_some_other_function(self):
        data = self.FixtureB.getData()
        # execute test case

Upvotes: 2

Views: 7404

Answers (2)

DV82XL
DV82XL

Reputation: 6639

The following code is based on the answer from @MrBeanBremen.

You can create a fixture in the base class called injector, whose sole responsibility it is to inject fixtures from conftest into the base class:

class BaseTest:

    @fixture(autouse=True)
    # noinspection PyAttributeOutsideInit
    def injector(self, fixtureA, fixtureB):
        self.fixtureA = fixtureA
        self.fixtureB = fixtureB

All test classes that inherit from BaseTest can now access the fixtures without any boilerplate code. A constructor cannot be used in BaseTest, since PyTest will ignore the test class completely if a constructor is implemented. PyCharm will generate a weak warning, since we're defining attributes outside __init__, but that can be suppressed using the noinspection comment.

Upvotes: 2

MrBean Bremen
MrBean Bremen

Reputation: 16825

First, you can define fixtures both in conftest.py and in test classes. The difference is visibility: if you define a fixture in a conftest.py, it is visible to all tests on the level of that conftest.py file and below. If you define it inside a test module, it is visible in this module only. If you define it inside a test class, it is visible in this class and derived classes.

Also note that you can use autotest=True also if you return a value - you just have to reference the fixture in the respective tests. You can also save the fixture value in a variable. Here is a simplistic example for both cases if you are are using a base class:

class TestBase:
    @pytest.fixture(autouse=True)
    def fixture1(self):
        self.value1 = 1  # save the fixture value
        yield

    @pytest.fixture
    def fixture2(self):
        yield 2  # return the fixture value - fixtue has to be referenced
        # autouse can still make sense if there is setup/tearDown code,
        # and the fixture must not be referenced in all of the tests

    @pytest.fixture(autouse=True)
    def fixture3(self):
        self.value3 = 3
        yield 3  # do both - can be used either way


class TestDerived(TestBase):
    def test_1(self):
        assert self.value1 == 1

    def test_2(self, fixture2):
        assert fixture2 == 2

    def test_3_1(self):
        assert self.value3 == 3

    def test_3_2(self, fixture3):
        assert fixture3 == 3

Note that you get the fixture value, not the fixture itself, if you refer to the fixture, so there is no need (and it is not possible) to call the fixture - instead you directly use the value returned by the fixture.

Upvotes: 4

Related Questions