Mock context managers#
In Python, with statement context managers provide an interface to execute code inside a given "runtime context." This context can define consistent, failsafe setup and teardown behavior. For example, Python's built-in file objects provide a context manager interface to ensure the underlying file resource is opened and closed cleanly, without the caller having to explicitly deal with it:
with open("hello-world.txt", "r") as f:
contents = f.read()
You can use Decoy to mock out your dependencies that provide a context manager interface.
Generator-based context managers#
Using the contextlib module, you can decorate a generator function or method to turn its yielded value into a context manager. To mock a generator function context manager, use Stub.then_enter_with.
@contextlib.contextmanager
def open_config(path: str) -> collections.abc.Iterator[bool]:
...
def test_generator_context_manager(decoy: Decoy) -> None:
mock_open_config = decoy.mock(func=open_config)
decoy
.when(mock_open_config)
.called_with("some_flag")
.then_enter_with(True)
with mock_open_config("some_flag") as result:
assert result is True
Class-based context managers#
You can stub out a context manager's __enter__ and __exit__ method like any other method.
def test_context_manager(decoy: Decoy) -> None:
subject = decoy.mock(name="cm")
decoy.when(subject.__enter__).called_with().then_return("hello world")
with subject as result:
assert result == "hello world"
decoy.verify(subject.__exit__).called_with(None, None, None)
This also works with asynchronous __aenter__ and __aexit__
Context manager state#
You can also configure stubs and verifications to only match calls made while a context manager mock is entered using the is_entered option to called_with.
is_entered |
Matching behavior |
|---|---|
True |
Only matches calls made between __enter__ and __exit__ |
False |
Only matches calls made if context manager is not entered |
None |
Match the call regardless of context manager entry state |
decoy
.when(subject.read, is_entered=True)
.called_with("some_flag")
.then_return(True)
decoy
.verify(subject.write, is_entered=True)
.called_with("some_flag", "new_value")