简体   繁体   中英

access python unittest magicmock return value

I'm using python 3.9.2 with unittest and mock to patch out a class.

My code under test instantiates an object of the class and mock returns a MagicMock object as the instance.

My question is, can I access that object from my test code?

I can see the call that instantiates the class in the mock_calls list, but cannot find a way of accessing the instance that is returned from that call.

The reason I need to access the instance is that my code under test attaches attributes to the instance rather than call methods on it. It is easy to test method calls, but is there a direct way to test attributes?

Upon investigation I found that there was only a single instance of a MagicMock being created and returned each time I instantiated my class. This behaviour was not convenient for me due to the attributes that I add to the class.

I created the following test aid to support my needs. This is not general-purpose but could be adapted for other circumstances.

class MockMyClass():
"""mock multiple MyClass instances
Note - the code under test must add a name attribute to each instance
"""

def __init__(self):
    self.myclass = []

def factory(self, /, *args, **kwargs):
    """return a new instance each time called"""
    new = mock.MagicMock()
    # override __enter__ to enable the with... context manager behaviour
    # for convenience in testing
    new.__enter__ = lambda x: new
    self.myclass.append(new)
    return new

def __getitem__(self, key: str) -> None:
    """emulate a dict by returning the named instance
    use as
    mockmyclass['name'].assert_called_once()
    or
    with mockmyclass['name'] as inst:
        inst.start().assert_called_once()
    """
    # Important - the code under test gives the instance a name
    # attribute and this relies on that attribute so is not
    # general purpose
    wanted = [t for t in self.myclass if t.name == key]
    if not wanted:
        names = [t.name for t in self.myclass]
        raise ValueError(f'no timer {key} in {names}')
    return wanted[0]

class TestBehaviour(unittest.TestCase):

def setUp(self):
    self.mockmyclass = MockMyClass()
    self.mocked = mock.patch(
        'path-to-my-file.MyClass',
        side_effect=self.mockmyclass.factory,
    )
    self.addCleanup(self.mocked.stop)
    self.mocked = self.mocked.start()

def test_something(self):
    # call code under test
    # then test with
    with self.mockmyclass['name-of-instance'] as inst:
        inst.start.assert_called_once()
        inst.stop.assert_called_once()
    # or test with
    self.mockmyclass['name-of-instance'].start.assert_called_once()

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