import pytest class Resource: def __init__(self): self.active = False def __enter__(self): self.active = True return self def __exit__(self, exc_type, exc_val, exc_tb): self.active = False @pytest.fixture def resource(): with Resource() as r: yield r def test_resource_active(resource): assert resource.active
The fixture uses a context manager to create and yield a Resource instance. Inside the with block, resource.active is set to True. The test runs while the resource is active, so the assertion passes.
import pytest class Resource: def __init__(self): self.active = False def __enter__(self): self.active = True return self def __exit__(self, exc_type, exc_val, exc_tb): self.active = False @pytest.fixture def resource(): with Resource() as r: yield r def test_resource_cleanup(resource): pass # Which assertion goes here?
The test runs while the resource is inside the with block, so resource.active is True. Cleanup happens after the test finishes.
import pytest class Resource: def __enter__(self): print('Entering') return self def __exit__(self, exc_type, exc_val, exc_tb): print('Exiting') @pytest.fixture def resource(): with Resource() as r: yield r print('After yield') def test_hang(resource): assert True
The misconception (option B) comes from thinking that yield inside with prevents the with block from exiting. In reality, yield suspends the fixture generator, runs the test, then resumes after yield (prints 'After yield'), and finally exits the with calling __exit__. No hang occurs.
1| import pytest 2| 3| class Resource: 4| def __enter__(self): 5| print('Enter') 6| return self 7| def __exit__(self, exc_type, exc_val, exc_tb): 8| print('Exit') 9| 10| @pytest.fixture 11| def resource(): 12| with Resource() as r: 13| yield r 14| print('Cleanup')
The yield statement on line 13 is where the fixture pauses and provides the resource to the test function. This is the best locator to identify the point where the test receives the fixture.
Pytest runs the fixture code up to the yield to set up the resource. It then runs the test. After the test finishes (pass or fail), pytest resumes the fixture and runs the code after the yield to clean up.