pytest fixture with scope session running for every test - python

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.

Related

Local import as pytest fixture?

I need to import some functions locally within my tests (yes, the code base can be designed better to avoid this necessity, but let's assume we can't do that).
That means the first line of all my tests within a module looks like in this example:
def test_something():
from worker import process_message
process_message()
Now I wanted to make this more DRY by creating the following fixture:
#pytest.fixture(scope="module", autouse=True)
def process_message():
from worker import process_message
return process_message
But I always get the error
Fixture "process_message" called directly. Fixtures are not meant to
be called directly, but are created automatically when test functions
request them as parameters. See
https://docs.pytest.org/en/stable/explanation/fixtures.html for more
information about fixtures, and
https://docs.pytest.org/en/stable/deprecations.html#calling-fixtures-directly
about how to update your code.
The linked documentation doesn't help me much.
How can I achieve what I want? I'd like to return the function handle obviously.
The reason this happens is that you are calling the fixture directly from one of your tests. I assume that your test code with the fixture looks something like this:
import pytest
#pytest.fixture(scope="module", autouse=True)
def process_message():
from worker import process_message
return process_message
def test_something():
process_message()
And then test_something fails with the specified exception.
The way to fix it is to add process_message as an argument to the test function, indicating that you are using it as a fixture:
def test_something(process_message):
process_message()
btw, since you have to specify process_message fixture in every test in order to call it, means there is no point in using the autouse=True and it can be removed.

Fixture order of execution in pytest

I'm new to using Pytest. I'm a little confused about what's the appropriate use of fixtures and dependency injection for my project. My framework is the following:
conftest.py
#pytest.fixture(scope="session")
def test_env(request):
//some setup here for the entire test module test_suite.py
#pytest.fixture(scope="function")
def setup_test_suite(test_env):
//some setup that needs to be done for each test function
//eventually parameterize? for different input configuration
test_suite.py
def test_function(setup_test_suite)
//some testing code here
What's the difference between putting the setup function in conftest.py or within the test suite itself?
I want the setup of the testing environment to be done once, for the entire session. If setup_test_suite is dependent on test_env, will test_env execute multiple times?
If I were to parameterize setup_test_suite, what would be the order of calling the fixtures?
The difference between a fixture in conftest.py and in the local test module is visibility. The fixture will be visible to all test modules at the same levels and in modules below that level. If the fixture is set to autouse=True it will be executed for all tests in these modules.
For more information, check What is the use of conftest.py files
If test_env is session-based, it will be executed only once in the session, even if it is referenced in multiple tests. If it yields an object, the same object will be passed to all tests referencing it.
For example:
#pytest.fixture(scope="session")
def global_fixt():
print('global fixture')
yield 42
#pytest.fixture(scope="function")
def local_fixt(global_fixt):
print('local fixture')
yield 5
def test_1(global_fixt, local_fixt):
print(global_fixt, local_fixt)
def test_2(global_fixt, local_fixt):
print(global_fixt, local_fixt)
Gives the output for pytest -svv:
...
collected 2 items
test_global_local_fixtures.py::test_1 global fixture
local fixture
42 5
PASSED
test_global_local_fixtures.py::test_2 local fixture
42 5
PASSED
...
Fixture parameters are called in the order they are provided to the fixture. if you have for example:
#pytest.fixture(scope="session", params=[100, 1, 42])
def global_fixt(request):
yield request.param
def test_1(global_fixt):
assert True
def test_2(global_fixt):
assert True
The tests will be executed in this order:
test_1 (100)
test_2 (100)
test_1 (1)
test_2 (1)
test_1 (42)
test_2 (42)

What are the functional differences in these 3 pytest fixtures?

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.

How does pytest deal with fixtures calling other fixtures?

I have two pytest fixtures, client and app. client calls app.
The test function test_register has arguments client and app and hence calls both fixtures.
My question is if the instance of app used in test_register is always going to be the one that client called, and if this is how pytest works in general (the assertion in test_register passes, so it is true in this case) .
In other words, does pytest generate unrelated instances for each argument in a test function that calls a fixture or does it call the fixtures and the instances returned also reference each other?
Here's the code:
#pytest.fixture
def app():
app = create_app({
'TESTING': True,
})
yield app
#pytest.fixture
def client(app):
return app.test_client()
def test_register(client, app):
assert client.application is app
All fixtures have a scope, the implicit scope being function but there's also class, module and session scopes. Within each scope there will only ever be one instance created of a fixture.
So in your example both app and client are using the function-scope. When executing test_register it enters the function-scope of this test and creates the fixture intances. Hence both test_register and client get the same instance of app.
See the docs for more details on how this all works.

Disable autouse fixtures on specific pytest marks

Is it possible to prevent the execution of "function scoped" fixtures with autouse=True on specific marks only?
I have the following fixture set to autouse so that all outgoing requests are automatically mocked out:
#pytest.fixture(autouse=True)
def no_requests(monkeypatch):
monkeypatch.setattr("requests.sessions.Session.request", MagicMock())
But I have a mark called endtoend that I use to define a series of tests that are allowed to make external requests for more robust end to end testing. I would like to inject no_requests in all tests (the vast majority), but not in tests like the following:
#pytest.mark.endtoend
def test_api_returns_ok():
assert make_request().status_code == 200
Is this possible?
You can also use the request object in your fixture to check the markers used on the test, and don't do anything if a specific marker is set:
import pytest
#pytest.fixture(autouse=True)
def autofixt(request):
if 'noautofixt' in request.keywords:
return
print("patching stuff")
def test1():
pass
#pytest.mark.noautofixt
def test2():
pass
Output with -vs:
x.py::test1 patching stuff
PASSED
x.py::test2 PASSED
In case you have your endtoend tests in specific modules or classes you could also just override the no_requests fixture locally, for example assuming you group all your integration tests in a file called end_to_end.py:
# test_end_to_end.py
#pytest.fixture(autouse=True)
def no_requests():
return
def test_api_returns_ok():
# Should make a real request.
assert make_request().status_code == 200
I wasn't able to find a way to disable fixtures with autouse=True, but I did find a way to revert the changes made in my no_requests fixture. monkeypatch has a method undo that reverts all patches made on the stack, so I was able to call it in my endtoend tests like so:
#pytest.mark.endtoend
def test_api_returns_ok(monkeypatch):
monkeypatch.undo()
assert make_request().status_code == 200
It would be difficult and probably not possible to cancel or change the autouse
You can't canel an autouse, being as it's autouse. Maybe you could do something to change the autouse fixture based on a mark's condition. But this would be hackish and difficult.
possibly with:
import pytest
from _pytest.mark import MarkInfo
I couldn't find a way to do this, but maybe the #pytest.fixture(autouse=True) could get the MarkInfo and if it came back 'endtoend' the fixture wouldn't set the attribute. But you would also have to set a condition in the fixture parameters.
i.e.: #pytest.fixture(True=MarkInfo, autouse=True). Something like that. But I couldn't find a way.
It's recommended that you organize tests to prevent this
You could just separate the no_requests from the endtoend tests by either:
limit the scope of your autouse fixture
put the no_requests into a class
Not make it an auto use, just pass it into the params of each def you need it
Like so:
class NoRequests:
#pytest.fixture(scope='module', autouse=True)
def no_requests(monkeypatch):
monkeypatch.setattr("requests.sessions.Session.request", MagicMock())
def test_no_request1(self):
# do stuff here
# and so on
This is good practice. Maybe a different organization could help
But in your case, it's probably easiest to monkeypatch.undo()

Categories