Reputation: 5285
Closely related: In python, is there a good idiom for using context managers in setup/teardown
I have a context manager that is used in tests to fix the time/timezone. I want to have it in a pytest funcarg (or fixture, we are using pytest
2.2.3 but I can translate backwards). I could just do this:
def pytest_funcarg__fixedTimezone(request):
# fix timezone to match Qld, no DST to worry about and matches all
# Eastern states in winter.
fixedTime = offsetTime.DisplacedRealTime(tz=' Australia/Brisbane')
def setup():
fixedTime.__enter__()
return fixedTime
def teardown(fixedTime):
# this seems rather odd?
fixedTime.__exit__(None, None, None)
... but it's a bit icky. In the related Q jsbueno points out: The problem is that your code has no provision to call the object's __exit__
method properly if an exception occurs.
His answer uses a metaclass approach. But this is not that useful for pytest where often tests are just functions, not classes. So what would be the pytest-y way to solve this? Something involving runtest hooks?
Upvotes: 18
Views: 19169
Reputation: 4704
Since 2.4, py.test
has yield
style fixture support. We could use a with
context inside it directly.
@pytest.yield_fixture
def passwd():
with open("/etc/passwd") as f:
yield f.readlines()
Since 3.0, py.test
deprecated the @pytest.yield_fixture
usage. We could use @pytest.fixture
as a context manager directly.
@pytest.fixture
def passwd():
with open("/etc/passwd") as f:
yield f.readlines()
Upvotes: 29
Reputation: 6357
I'm afraid there is currently no elegant way of using context managers in fixtures. However the finalizers will run if the test fails:
import contextlib, pytest
@contextlib.contextmanager
def manager():
print 'manager enter'
yield 42
print 'manager exit'
@pytest.fixture
def fix(request):
m = manager()
request.addfinalizer(lambda: m.__exit__(None, None, None))
return m.__enter__()
def test_foo(fix):
print fix
raise Exception('oops')
If you run this with pytest -s
you will see that the __exit__()
call happens.
Upvotes: 0