简体   繁体   English

访问 python unittest magicmock 返回值

[英]access python unittest magicmock return value

I'm using python 3.9.2 with unittest and mock to patch out a class.我正在使用带有 unittest 和 mock 的 python 3.9.2 来修补 class。

My code under test instantiates an object of the class and mock returns a MagicMock object as the instance.我的测试代码实例化了 class 的 object 并模拟返回 MagicMock object 作为实例。

My question is, can I access that object from my test code?我的问题是,我可以从我的测试代码中访问那个 object 吗?

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.我可以在 mock_calls 列表中看到实例化 class 的调用,但找不到访问从该调用返回的实例的方法。

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.经过调查,我发现每次实例化我的 class 时,只有一个 MagicMock 实例被创建并返回。 This behaviour was not convenient for me due to the attributes that I add to the class.由于我添加到 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): class 测试行为(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()

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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