I am trying to patch some context manager function using a Mock so I can test that the code does sensible things given good, bad, and garbage input. Here is the test code with the with
statement in it. The patch is done in the correct place in my code.
@patch("__main__.opened_w_error")
def test_get_recipe_file(self, mo):
mo.return_value = (Mock(), None)
mo.__enter__ = Mock(return_value=None)
mo.__exit__ = Mock(return_value=None)
with mo(…) as (fd, err): # AttributeError: __exit__ is raised here.
print(fd)
print(err)
However the with mo(…) as (fd, err)
raises AttributeError: __exit__
.
The documentation for mocking magic methods states the you should use it as
with mo as (fd, err):
…
The latter piece of code is what I am trying to mock.
but that is not how I use it in my code. For those really interested, I am trying to mock example 6 opened_w_error()
in PEP 343 which deals with opening files and catching errors. Thus the code is:
with open_w_error(filename, 'r') as (fd, err):
…
The latter is what I am trying to mock.
Note that the object you pass to the with statement is the one that should have __enter__
and __exit__
methods, with the return value from __enter__
used for the as
construct. In your case, you are calling mo(...)
, which returns (Mock(), None)
, and this is not a context manager. You should move this return value to the __enter__
method.
@patch("__main__.opened_w_error")
def test_get_recipe_file(self, mo):
mo.__enter__ = Mock(return_value=(Mock(), None))
mo.__exit__ = Mock(return_value=None)
with mo as (fd, err):
print(fd)
print(err)
EDIT: If you still want to call mo
, then make its return value a context manager.
@patch("__main__.opened_w_error")
def test_get_recipe_file(self, m_opened_w_error):
mo = Mock()
mo.__enter__ = Mock(return_value=(Mock(), None))
mo.__exit__ = Mock(return_value=None)
m_opened_w_error.return_value = mo
with m_opened_w_error(...) as (fd, err):
print(fd)
print(err)
The issue is when you're calling mo(..)
the object it is returning(tuple
) doesn't have __enter__
and __exit__
attributes on it.
To fix this assign mo
to mo
's return_value
so that the context manager attributes you're setting on it can still be found.
@patch("__main__.opened_w_error")
def test_get_recipe_file(self, mo):
mo.return_value = mo
mo.__enter__ = Mock(return_value=(Mock(), None))
mo.__exit__ = Mock(return_value=None)
with mo('file', 'r') as (fd, err):
print(fd)
print(err)
mo.assert_called_once_with('file', 'r') # Should be True
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With