14

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?

Løiten
  • 3,185
  • 4
  • 24
  • 36
pfctdayelise
  • 5,115
  • 3
  • 32
  • 52

2 Answers2

22

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()
Jiangge Zhang
  • 4,298
  • 4
  • 25
  • 33
1

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.

Zearin
  • 1,474
  • 2
  • 17
  • 36
flub
  • 5,953
  • 27
  • 24
  • 1
    My concern is not that the `__exit__` may not be called, but that it won't be called with the [right values](http://docs.python.org/2/reference/datamodel.html#context-managers). `__exit__` is normally called with values relating to any exception raised in the `with` block (or in this case it would be the test body). – pfctdayelise Apr 05 '13 at 01:19