What are the functional differences in these 3 pytest fixtures? - python

I'm relatively new to pytest-style unit testing, and I'm trying to learn more about pytest fixtures. I'm not passing a scope argument to the fixture, so I know that the scope is "function". Are there any functional differences in these 3 styles of simple fixtures? Why would one approach be favored over the others?
#pytest.fixture()
#patch('a.b.c.structlog.get_logger')
def fixture_classQ(mock_log):
handler = MagicMock(spec=WidgetHandler)
return ClassQ(handler)
#pytest.fixture()
def fixture_classQ():
with patch('a.b.c.structlog.get_logger'):
handler = MagicMock(spec=WidgetHandler)
return ClassQ(handler)
#pytest.yield_fixture()
def fixture_classQ():
with patch('a.b.c.structlog.get_logger'):
handler = MagicMock(spec=WidgetHandler)
yield ClassQ(handler)
Simple example usage of the fixture:
def test_classQ_str(fixture_classQ):
assert str(fixture_classQ) == "This is ClassQ"
Thanks.

fixture 1
Starting with the first one, this creates a plain-data fixture. The mock is (imo misleadingly) only alive for the duration of the fixture function because it uses return.
In order ~roughly what happens for that:
pytest notices your fixture is used for the test function
it calls the fixture function
the mock decorator starts the patch
the mock decorator calls your actual function (which returns a value)
the mock decorator undoes the patch
pytest notices it wasn't a generator and so that's the value of your fixture
fixture 2
the second is identical in behaviour to the first, except it uses the context manager form of mock instead of the decorator. personally I don't like the decorator form but that's just me :D
fixture 3
(first before I continue, pytest.yield_fixture is a deprecated alias for pytest.fixture -- you can just use #pytest.fixture)
The third does something different! The patch is alive for the duration of the test because it has "yielded" during the fixture. This is a kind of way to create a setup + teardown fixture all in one. Here's roughly the execution here
pytest notices your fixture is used for the test function
pytest calls the fixture function
since it is a generator, it returns immediately without executing code
pytest notices it is a generator, calls next(...) on it
this causes the code to execute until the yield and then "pausing". you can think of it kind of as a co-routine
the __enter__ of the mock is called making the patch active
the value that is yielded is used as the value of the fixture
pytest then executes your test function
pytest then calls next(...) again on the generator to exhaust the fixture
this __exit__s the with statement, undoing the patch
which to choose?
the best answer is it depends. Since 1 and 2 are functionally equivalent it's up to personal preference. Pick 3. if you need the patch to be active during the entire duration of your test. And don't use pytest.yield_fixture, just use pytest.fixture.

Related

How does a pytest mocker work given there is no import statement for it?

I am following this mini-tutorial/blog on pytest-mock. I can not understand how the mocker is working since there is no import for it - in particular the function declaration def test_mocking_constant_a(mocker):
import mock_examples.functions
from mock_examples.functions import double
def test_mocking_constant_a(mocker):
mocker.patch.object(mock_examples.functions, 'CONSTANT_A', 2)
expected = 4
actual = double() # now it returns 4, not 2
assert expected == actual
Somehow the mocker has the attributes/functions of pytest-mocker.mocker: in particular mocker.patch.object . But how can that be without the import statement?
The mocker variable is a Pytest fixture. Rather than using imports, fixtures are supplied using dependency injection - that is, Pytest takes care of creating the mocker object for you and supplies it to the test function when it runs the test.
Pytest-mock defines the "mocker" fixture here, using the Pytest fixture decorator. Here, the fixture decorator is used as a regular function, which is a slightly unusual way of doing it. A more typical way of using the fixture decorator would look something like this:
#pytest.fixture()
def mocker(pytestconfig: Any) -> Generator[MockerFixture, None, None]:
"""
Return an object that has the same interface to the `mock` module, but
takes care of automatically undoing all patches after each test method.
"""
result = MockerFixture(pytestconfig)
yield result
result.stopall()
The fixture decorator registers the "mocker" function with Pytest, and when Pytest runs a test with a parameter called "mocker", it inserts the result of the "mocker" function for you.
Pytest can do this because it uses Python's introspection features to view the list of arguments, complete with names, before calling the test function. It compares the names of the arguments with names of fixtures that have been registered, and if the names match, it supplies the corresponding object to that parameter of the test function.

pytest unittest custom decorator change unittest execution order

I am using pytest to test firmware functionality on a microcontroller. The interface to it is irrelevant for this conversation.
As such, I have a number of checks which I include at the end of every unittest to ensure that the microcontroller is still in a valid state.
Now, these checks are common to every unittest, to be executed at the end of execution of each unittest. I thought it would make sense to use a decorator for this and decorate each unittest instead of duplicating code.
This works except now, unittest execution is no longer in sequence. Unittests now seem to be executed randomly instead of the order in which they were defined which was the behavior before I introduced the decorator.
Could someone please tell me why this happening?
Example pseudocode:
def check_no_err(func):
def checker(api, *args, **kwargs):
func(api, *args, **kwargs)
assert 1 #<some checker code here run after the unittest>
#check_no_err
def test_eval_status(api):
assert 1 #unittest body here
edit: I am using a fixture with a function scope to achieve the same behavior which provides predictable unit test execution order. Like this:
#pytest.fixture(scope='function')
def test_end_checker(api):
assert 1 #the check to run after every test which includes this fixture
So the only remaining question is why execution order is being randomized due to the additional decorator.

How to evade non-called fixture in Pytest?

Pytest suite has a brilliant feature of fixtures.
To make a reusable fixture, we mark a function with special decorator:
#pytest.fixture
def fix():
return {...}
It can later be used in our test through an argument name matching the original name of the fixture:
def test_me(fix):
fix['field'] = 'expected'
assert(fix['field'] == 'expected')
Although from time to time we might forget to specify the fixture in the arguments, and, since the name of the factory matches the name of the object produced, the test will silently apply changes to the factory object itself:
def test_me(): # notice no arg
fix['this is'] = 'a hell to debug'
Certainly, the outcome is undesirable. It would be nice, for instance, to be able to add some suffix to factory function, but the pytest.fixture decorator apparently does not have a means to override the name for the fixture.
Any other advice would suffice as well.
What is a recommended technique to protect ourselves from this kind of issue?
You can use autouse=True while defining fixture to invoke the fixture every time the scope of fixture starts. This feature was added in pytest 2.0.
For example:
import pytest
#pytest.fixture(scope='function',autouse=True)
def fixture_a():
return 5
def test_a():
assert fixture_a == 5
As you can see, I did not have to declare fixture as an argument in test_a to access it.
Documentation: https://docs.pytest.org/en/latest/reference.html#pytest-fixture
Code example: https://docs.pytest.org/en/latest/fixture.html#autouse-fixtures-xunit-setup-on-steroids

Python patch decorator spilling into other methods

My understanding is that when you use a patch decorator on a unit test (I'm using nose framework), the scope of that patch decorator is the scope of the test case. Here's the issue:
#patch('a')
#patch('b')
#patch('b')
def test_add_stuff(self, mock_a, mock_b, mock_c):
url = '/path/blah/blah'
assert_true(stuff)
# Now those three patch decorators should be "done"
def test_delete_stuff(self):
url = '/another_path/not_important'
assert_true(something)
In my second test case, test_delete_stuff, I have added a print statement in the actual code in an effort to debug an error that was being thrown. Turns out, one of the function calls within the controller action being hit through the url is returning a MagicMock object! It's mock_b from the previous test case.
If I simply reverse the order of the two test cases, nothing changes. If I comment out the one with the patch decorators, my second test case passes.
Additional info:
There are no patch decorators on the class that these instance methods are under.
Any ideas?
--UPDATE--
It turns out that I didn't mock my function calls from where they were being looked up, and that fixed the issue. However, it does not explain why the scope of the patch exceeded the one test case.
If the controller gets instantiated only when the GET request gets sent using app.get, and the imports in the controller file get mocked, why would the MagicMock object persist through multiple unit tests?
I guess the issue with the path scope has appeared, because you patched methods in the TestCase class. You can find in the official Python unittest.patch documentation:
Patch can be used as a TestCase class decorator. It works by decorating each test method in the class. This reduces the boilerplate code when your test methods share a common patchings set. patch() finds tests by looking for method names that start with patch.TEST_PREFIX. By default this is 'test', which matches the way unittest finds tests. You can specify an alternative prefix by setting patch.TEST_PREFIX.
So all the methods with the prefix test will be decorated with the patches. This is the default behavior.

pytest fixture with scope session running for every test

Correct me if I'm wrong, but if a fixture is defined with scope="session", shouldn't it be run only once per the whole pytest run?
For example:
import pytest
#pytest.fixture
def foo(scope="session"):
print('foooooo')
def test_foo(foo):
assert False
def test_bar(foo):
assert False
I have some tests that rely on data retrieved from some APIs, and instead of querying the API in each test, I rather have a fixture that gets all the data at once, and then each test uses the data it needs. However, I was noticing that for every test, a request was made to the API.
That's because you're declaring the fixture wrong. scope should go into the pytest.fixture decoraror parameters:
#pytest.fixture(scope="session")
def foo():
print('foooooo')
In your code, the scope is left to default value function, that's why the fixture is being ran for each test.

Categories