Let’s say I’ve got:
- a function scope fixture ‘resource_c’
- that uses a module scoped fixture ‘fixture_b’
- that uses a session scoped fixture ‘fixture_a’
This all works fine.
conftest.py:
import pytest
@pytest.fixture(scope="session")
def resource_a():
print('\n[setup] resource_a()')
yield
print('[teardown] resource_a()')
@pytest.fixture(scope="module")
def resource_b(resource_a):
print('[setup] resource_b()')
yield
print('[teardown] resource_b()')
@pytest.fixture(scope="function")
def resource_c(resource_b):
print('[setup] resource_c()')
yield
print('\n[teardown] resource_c()')
test_alpha.py:
def test_one(resource_c):
print('In test_one()')
def test_two(resource_c):
print('In test_two()')
test_beta.py:
def test_three(resource_c):
print('In test_three()')
def test_four(resource_c):
print('In test_four()')
This seems reasonable to me.
What do you think will happen?
output:
$ pytest -s -v
================== test session starts ==================
collected 4 items
test_alpha.py::test_one
[setup] resource_a()
[setup] resource_b()
[setup] resource_c()
In test_one()
PASSED
[teardown] resource_c()
test_alpha.py::test_two [setup] resource_c()
In test_two()
PASSED
[teardown] resource_c()
[teardown] resource_b()
test_beta.py::test_three [setup] resource_b()
[setup] resource_c()
In test_three()
PASSED
[teardown] resource_c()
test_beta.py::test_four [setup] resource_c()
In test_four()
PASSED
[teardown] resource_c()
[teardown] resource_b()
[teardown] resource_a()
=================== 4 passed in 0.01s ===================
The above should make sense. But it might not at first.
It’s good to play with this yourself to get the hang of fixture scope.
Why is it important? Because it’s super powerful to push common code into fixtures, then common code from fixtures into other fixtures, etc. And if you know how to use this, it makes building test suites that much easier.
Using --setup-show
to debug scope
We don’t need to use print statements to trace fixture scope.
pytest has a flag called --setup-show
that shows scope very well.
$ pytest -v --setup-show
===================================== test session starts =====================================
collected 4 items
test_alpha.py::test_one
SETUP S resource_a
SETUP M resource_b (fixtures used: resource_a)
SETUP F resource_c (fixtures used: resource_b)
test_alpha.py::test_one (fixtures used: resource_a, resource_b, resource_c)PASSED
TEARDOWN F resource_c
test_alpha.py::test_two
SETUP F resource_c (fixtures used: resource_b)
test_alpha.py::test_two (fixtures used: resource_a, resource_b, resource_c)PASSED
TEARDOWN F resource_c
TEARDOWN M resource_b
test_beta.py::test_three
SETUP M resource_b (fixtures used: resource_a)
SETUP F resource_c (fixtures used: resource_b)
test_beta.py::test_three (fixtures used: resource_a, resource_b, resource_c)PASSED
TEARDOWN F resource_c
test_beta.py::test_four
SETUP F resource_c (fixtures used: resource_b)
test_beta.py::test_four (fixtures used: resource_a, resource_b, resource_c)PASSED
TEARDOWN F resource_c
TEARDOWN M resource_b
TEARDOWN S resource_a
====================================== 4 passed in 0.01s ======================================
It even puts S for session scope, M for module scope, and F for function scope.
A nice thing about using --setup-show
for debugging a test suite is that you don’t have to fiddle with which print statement needs to start with a newline to get the formatting nice. :)
WARNING: you gotta use bigger and bigger scope
If you do this in the wrong order, things go haywire.
Let’s swap scope on a couple of items.
conftest.py:
...
@pytest.fixture(scope="module") # session -> module
def resource_a(request):
print('\n[setup] resource_a()')
yield
print('\n[teardown] resource_a()')
@pytest.fixture(scope="session") # module -> session
def resource_b(request, resource_a):
...
We will get some warning like this (or several):
E ScopeMismatchError: You tried to access the 'module' scoped fixture 'resource_a'
... with a 'session' scoped request object, involved factories
E conftest.py:18: def resource_c(request, resource_b)
E conftest.py:10: def resource_b(request, resource_a)
E conftest.py:2: def resource_a(request)
So. Don’t do that.
Warning applies to built in fixtures
pytest includes many built in fixtures. Most are function scoped.
These cannot be used, therefore, by fixtures scoped wider than function.
This post is part of a series on pytest fixtures
- pytest fixtures nuts and bolts
- pytest xunit-style fixtures
- Basic pytest fixtures example
- Using pytest fixtures by naming them
- Using pytest autouse fixtures
- Using pytest fixtures with mark.usefixtures
- pytest fixture return value
- pytest fixture teardown
- pytest fixture scope
- Parametrizing pytest fixtures with param
- Using multiple pytest fixtures
- Modularity: pytest fixtures using other fixtures
- pytest session scoped fixtures
- pytest fixtures nuts and bolts
- Mixing pytest fixture scope) - this post