Why would a pytest factory as fixture be used over a factory function?
Asked Answered
T

4

25

In the py.test docs it describes declaring factory methods as fixtures, like-so:

@pytest.fixture
def make_foo():
    def __make_foo(name):
        foo = Foo()
        foo.name = name
        return foo
    return __make_foo

What are the benefits/tradeoffs of doing this over just defining a make_foo function and using that? I don't understand why it is a fixture.

Tequila answered 2/8, 2018 at 23:15 Comment(2)
One advantage comes up when you want to reuse the factory in different test modules as you don't have to deal with imports, the other one is the teardown in yield fixtures (or ones that add the finalizer hooks to request), so you can rollback any changes the factory did throughout the test execution. These benefits are not factory specific and apply to any test helper function that could be replaced by a fixture.Thinker
Hey @codeblooded, don't you think you should accept my answer?Gasometer
G
21

Actually, the most important advantage is being able to use other fixtures, and make the dependency injection of pytest work for you. The other advantage is allowing you to pass parameters to the factory, which would have to be static in a normal fixture.

Look at this example:

@pytest.fixture
def mocked_server():
    with mock.patch('something'):
        yield MyServer()


@pytest.fixture
def connected_client(mocked_server):
    client = Client()
    client.connect_to(mocked_server, local_port=123)  # local_port must be static
    return client

You could now write a test that gets a connected_client, but you can't change the port. What if you need a test with multiple clients? You can't either.

If you now write:

@pytest.fixture
def connect_client(mocked_server):
    def __connect(local_port):
        client = Client()
        client.connect_to(mocked_server, local_port)
        return client
    return __connect

You get to write tests receiving a connect_client factory, and call it to get an initialized client in any port, and how many times you want!

Gasometer answered 3/8, 2018 at 0:5 Comment(2)
This shows the advantage of a factory fixture over a normal fixture, but how does it show the advantage of a factory fixture over a factory function?Approximal
A factory fixture allows much more composability: e.g. you can make use of any other fixtures here (local injection, the test is agnostic to it), you can parameterize this or any used fixtures in the chain (and pytest will correctly explode all their combinations), and minor but you do not need to import anything in test modules (just make the fixture available in your conftest.py and use anywhere).Gasometer
M
7

If you have many simple factories then you can simplify their creation with decorator:

def factory_fixture(factory):
    @pytest.fixture(scope='session')
    def maker():
        return factory

    maker.__name__ = factory.__name__
    return maker


@factory_fixture
def make_stuff(foo, bar):
    return 'foo' + str(foo + bar)

this is equivalent of

@pytest.fixture(score='session')
def make_stuff():
    def make(foo, bar):
        return 'foo' + str(foo + bar)
    return
Manchu answered 12/12, 2019 at 12:15 Comment(0)
H
3

One example might be a session-level fixture, e.g.:

@pytest.fixture(scope="session")
def make_foo():
    def __make_foo(name):
        foo = Foo()
        foo.name = name
        return foo
    return __make_foo

This way, Pytest will ensure that only one instance of the factory exists for the duration of your tests. This example in particular perhaps doesn't gain much from this, but if the outer function does a lot of processing, such as reading from a file or initialising data structures, then this can save you a lot of time overall.

Heaver answered 2/8, 2018 at 23:20 Comment(0)
H
-3

see below code, this gives ans to your questions..

import pytest
@pytest.fixture
def make_foo():
    def __make_foo(name):
        print(name)
    return __make_foo

def test_a(make_foo):
    make_foo('abc')

def test_b(make_foo):
    make_foo('def')

The output is shown below ::

tmp3.py::test_a abc
PASSED
tmp3.py::test_b def
PASSED

basically you can pass the arguments in factory fixture and can use according to your requirement.

Helprin answered 6/11, 2019 at 10:53 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.