模拟一个返回MagicMock的属性调用,而不是值

时间:2017-06-16 15:07:11

标签: python unit-testing properties mocking

我有以下配置类:

class ConfigB(object):
  Id = None

  def __Init__(self, Id):
    self.Id = Id

在以下类中实例化并打印属性:

from config.ConfigB import ConfigB

class FileRunner(object):
  def runProcess(self, Id)
    cfgB = ConfigB(Id)
    print(cfgB.Id)

我创建了以下测试类来测试它,我试图模拟实例化和cfgB.Id属性调用:

import unittest
import unittest.mock imort MagicMock
import mock
from FileRunner import FileRunner

class TestFileRunner(unittest.TestCase):
  @mock.patch('ConfigB.ConfigB.__init__')
  @mock.patch('ConfigB.ConfigB.Id')
  def test_methodscalled(self, cfgBId, cfgBInit):


    fileRunner = FileRunner()

    cfgBId.return_value = 17

    cfgBInit.return_value = None

    print(cfgBId)

    fileRunner.runProcess(1)

注意调用fileRunner之前的print(cfgBId)语句。我得到以下输出:

<MagicMock name='Id' id='157297352'>
<MagicMock name='Id' id='157297352'>

出于某种原因,我在测试类中设置了返回值:

cfgBId.return_value = 17

在FileRunner()类中没有调用它:

print(cfgB.Id)

要正确获取配置属性,我需要做什么?

另请注意我的配置&#39;类实例化比上面显示的要复杂得多,这就是为什么我要修补实例化和调用Id属性的原因。

*更新:我按照@mgilson的建议改变了我的课程,但它仍然无效:

import unittest
import unittest.mock imort MagicMock
import mock
from FileRunner import FileRunner

class TestFileRunner(unittest.TestCase):
  @mock.patch('FileRunner.ConfigB')
  def test_methodscalled(self, cfgB):

    fileRunner = FileRunner()

    cfgB.Id = 17

    print(cfgBId)

    fileRunner.runProcess(1)

我现在从两个打印语句中获得以下输出:

<MagicMock name='ConfigB' id='157793640'>
<MagicMock name='ConfigB().Id' id='157020512'>

为什么上述行为无效?

*解决方案:

在对@mgilson建议的测试方法稍作修改之后,我能够让它工作:

import unittest
from unittest.mock import MagicMock
import mock
from FileRunner import FileRunner

class TestFileRunner(unittest.TestCase):
  @mock.patch('FileRunner.ConfigB')
  def test_methodscalled(self, configB):

    fileRunner = FileRunner()

    cfgB = MagicMock()
    cfgB.Id = 17
    #This will return the cfgB MagicMock when `ConfigB(Id)` is called in `FileRunner` class
    configB.return_value = cfgB 

    print(cfgB.Id)

    fileRunner.runProcess(1)

    #To test whether `ConfigB(17)` was called
    configB.assert_called_with(17)

我现在得到以下输出:

<MagicMock name='ConfigB' id='157147936'>
17

3 个答案:

答案 0 :(得分:1)

在我看来,只需替换FileRunner命名空间中的整个ConfigB对象会更好。然后你的测试看起来像这样:

import unittest
import unittest.mock imort MagicMock
import mock
from FileRunner import FileRunner

class TestFileRunner(unittest.TestCase):
  @mock.patch('FileRunner.ConfigB')
  def test_methodscalled(self, cfgB):
    fileRunner = FileRunner()
    cfgB.return_value.Id = 17
    fileRunner.runProcess(1)

请注意,@mock.patch('FileRunner.ConfigB')将使用模拟替换ConfigB命名空间中的FileRunner类。然后,您可以配置模拟以执行您喜欢的任何操作 - 例如有Id等于17。

答案 1 :(得分:0)

要模拟Id属性,您可以使用Mock实例化的Mock修补类,如下所示:

@mock.patch('ConfigB.ConfigB', Mock(Id='17'))
def test_methodscalled(self, cfgB):
    cfgB.return_value.__init__.return_value = None # FWIW, this isn't necessary

答案 2 :(得分:0)

最近,我遇到了一个稍微复杂的问题,通过为return_value中使用的模拟设置mock.patch来解决类似的问题(如下所示)。

您可以进一步重构您的解决方案,以将mock.patch用作context manager(链接部分中的Ctrl-F“带有修补程序”)代替装饰器,从而对使用的对象进行更多控制:

class TestFileRunner(unittest.TestCase):
  def test_methodscalled(self):
    id = 17

    with mock.patch('FileRunner.ConfigB', return_value=Mock(Id=id)) as configB:
        FileRunner().runProcess(1)

    configB.assert_called_with(id)

我的情况:断言该调用是使用修补的对象实例进行的。

我想在某些类中测试条件日志记录(简化的代码示例)。经过测试的模块:

# tested_module.py
import logging
from exceptions import CustomExceptionClass

class ClassUnderTest:
    logger = logging.getLogger(__name__)
    def tested_instance_method(self, arguments):
        ...  # Some more code.
        if 2 in arguments:
            self.logger.exception(CustomExceptionClass(arguments))
        ...  # Some more code.

正在测试(解决问题后):

# test_module.py
from unittest import mock
import tested_module

class TestModule:
    def test_exception_logged(self):
        method_arguments = 1, 2, 3
        logger_mock = mock.Mock()
        tested_class_instance = mock.Mock(logger=logger_mock)
        exception_mock_instance = mock.Mock()

        with mock.patch.object(tested_module, 'CustomExceptionClass',
                               return_value=exception_mock_instance) as exception_mock:
            tested_module.ClassUnderTest.tested_instance_method(tested_class_instance,
                                                                method_arguments)

            logger_mock.exception.assert_called_once()
            self.assertSequenceEqual(logger_mock.exception.call_args_list,
                                     [mock.call(exception_mock_instance)])
            exception_mock.assert_called_once()
            self.assertSequenceEqual(exception_mock.call_args_list,
                                     [mock.call(method_arguments)])

在我提出上述解决方案之前,有问题的部分是:

# Check below fails.
self.assertSequenceEqual(logger_mock.exception.call_args_list,
                         [mock.call(exception_mock)])  # Wrong object, "()" problem.

然后我尝试了:

self.assertSequenceEqual(logger_mock.exception.call_args_list,
                         [mock.call(exception_mock())])  # Can't do `exception_mock()` ...
# ... because it was called 2nd time and assertion below will fail.
exception_mock.assert_called_once()

问题是我必须以某种方式获取另一个模拟对象,该对象在调用exception_mock且没有第二次调用exception_mock时返回。因此,预先创建mock.Mock实例并分配给return_value是正确的答案。