pytest is a great test runner, and is the one Hypothesis itself uses for testing (though Hypothesis works fine with other test runners too).
It has a fairly elaborate fixture system, and people are often unsure how that interacts with Hypothesis. In this article we'll go over the details of how to use the two together.
Mostly, Hypothesis and pytest fixtures don't interact: Each just ignores the other's presence.
When using a @given decorator, any arguments that are not provided in the @given will be left visible in the final function:
from inspect import signature
from hypothesis import given, strategies as st
@given(a=st.none(), c=st.none())
def test_stuff(a, b, c, d):
pass
print(signature(test_stuff))
This then outputs the following:
<Signature (b, d)>
We've hidden the arguments 'a' and 'c', but the unspecified arguments 'b' and 'd' are still left to be passed in. In particular, they can be provided as pytest fixtures:
from pytest import fixture
from hypothesis import given, strategies as st
@fixture(scope="module")
def stuff():
return "kittens"
@given(a=st.none())
def test_stuff(a, stuff):
assert a is None
assert stuff == "kittens"
Note the explicit scope="module": pytest fixtures default to function scope,
which Hypothesis rejects for @given tests - see the end of this article for
why, and what to do about it.
This also works if we want to use @given with positional arguments:
from pytest import fixture
from hypothesis import given, strategies as st
@fixture(scope="module")
def stuff():
return "kittens"
@given(st.none())
def test_stuff(stuff, a):
assert a is None
assert stuff == "kittens"
The positional argument fills in from the right, replacing the 'a' argument and leaving us with 'stuff' to be provided by the fixture.
Personally I don't usually do this because I find it gets a bit confusing - if I'm going to use fixtures then I always use the named variant of given. There's no reason you can't do it this way if you prefer though.
@given also works fine in combination with parametrized tests:
import pytest
from hypothesis import given, strategies as st
@pytest.mark.parametrize("stuff", [1, 2, 3])
@given(a=st.none())
def test_stuff(a, stuff):
assert a is None
assert 1 <= stuff <= 3
This will run 3 tests, one for each value for 'stuff'.
There is one unfortunate feature of how this interaction works though: In pytest you can declare fixtures which do set up and tear down per function. These will "work" with Hypothesis, but they will run once for the entire test function rather than once for each time given calls your test function. So the following will fail:
from pytest import fixture
from hypothesis import given, strategies as st
counter = 0
@fixture(scope="function")
def stuff():
global counter
counter = 0
@given(a=st.none())
def test_stuff(a, stuff):
global counter
counter += 1
assert counter == 1
The counter will not get reset at the beginning of each call to the test function, so it will be incremented each time and the test will start failing after the first call.
Update, 2026: the rest of this article has been rewritten, because Hypothesis now detects this problem for you.
Rather than letting a test like the one above quietly do the wrong thing,
Hypothesis raises a health check error whenever a test using @given requests
a function-scoped fixture - including pytest fixtures declared with no explicit
scope, since function scope is the default:
FailedHealthCheck: 'tests.py::test_stuff' uses a function-scoped fixture 'stuff'.
Function-scoped fixtures are not reset between inputs generated by
`@given(...)`, which is often surprising and can cause subtle test bugs.
So instead of a silently unsound test, you get an immediate error and a choice about how to proceed.
If you need set up and tear down for each generated input, do it inside the test function, for example with a context manager:
from contextlib import contextmanager
from hypothesis import given, strategies as st
@contextmanager
def fresh_stuff():
yield "kittens" # set up before this line, and tear down after
@given(a=st.none())
def test_stuff(a):
with fresh_stuff() as stuff:
assert a is None
assert stuff == "kittens"
If the fixture value is safe to reuse across inputs, declare it with a wider
scope such as module or session and use it exactly as shown earlier in
this article.
And if running the fixture once per test function really is what you want, tell Hypothesis that you've thought about this and it's OK:
from hypothesis import HealthCheck, given, settings, strategies as st
@settings(suppress_health_check=[HealthCheck.function_scoped_fixture])
@given(a=st.none())
def test_stuff(a, stuff):
assert a is None
Running fixtures once per generated input would require changes on the pytest side as well as the Hypothesis side, and still isn't supported - but with the health check to catch mistakes and the patterns above to express each intent, in practice this is no longer a problem.