简体   繁体   中英

Mock for context-manager fails with AttributeError: __exit__

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

What you need is this:

with mock.patch('__main__.opened_w_error') as mo:
    mo.__enter__ = Mock(return_value=(Mock(), None))
    mo.__exit__ = Mock(return_value=None)
    # Your code goes here

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM