如何测试在对象构造期间调用导入模块上的方法

How can I test that a method on an imported module is called during object construction

我正在开发一个 Reddit 机器人来学习 python 中的 TDD。

我有一个带有 class 的模块,它是这样的:

from praw import Reddit


class Bot():
    def __init__(self):
        self.reddit = Reddit(user_agent='myBot')
        self.reddit.login('fake', 'fakePassword')

在我的测试套件中,我有一个这样的设置方法:

@patch('bot.bot.Reddit.login')
def setUp(self, mocked_reddit):
    self.mocked_reddit = mocked_reddit
    self.subject = Bot()

..和这样的测试:

def should_call_reddit_login_when_initialized_test(self):
        self.assertTrue(self.mocked_reddit.assert_called_with('fake', 'fakePassword'))

我的补丁似乎只部分起作用。它实际上并没有调用 Reddit API 这很好。然而,断言总是错误的。

line 22 in should_call_reddit_login_when_initialized_test
      self.assertTrue(self.mocked_reddit.assert_called_with('fake', 'fakePassword'))
   AssertionError: None is not true

理想情况下,我可以模拟整个 Reddit class 并断言稍后使用预期参数调用这些方法。我怎样才能做到这一点?

assert_called_with 断言。只要那没有触发,即引发 AssertionError 异常,那么你就可以了。你不需要检查它的 return 值,它总是 None.

你问的是两件不同的事情:

  1. 为什么我的断言不起作用
  2. 如何模拟整个 Reddit

对于 1 @wim 答案是正确的并告诉你如何修复它:将你的测试线替换为:

self.mocked_reddit.assert_called_with('fake', 'fakePassword')

assert_* mock 的调用在 assert 失败时已经引发异常。

要处理 2,您应该改为修补 'bot.bot.Reddit',并考虑到您的对象将是模拟的 return_value。在这种情况下,我强烈建议使用 autospec=True 来保留 Reddit 完整的签名,请查看 Autospeccing 以了解更多详细信息。

在这种情况下,您的测试变为:

@patch('bot.bot.Reddit', autospec=True)
def setUp(self, mocked_reddit_class):
    self.mocked_reddit_class = mocked_reddit_class
    self.mocked_reddit = mocked_reddit_class.return_value
    self.subject = Bot()

def should_call_reddit_login_when_initialized_test(self):
    self.mocked_reddit.login.assert_called_with('fake', 'fakePassword')

在这种情况下,您应该注意使用 self.mocked_reddit 检查对象方法,并使用 self.mocked_reddit_class 检查静态和 class 方法。举个简单的例子:

import unittest
from unittest.mock import patch

class A():
    def b(self, a, b):
        pass
    @classmethod
    def c(cls,a,b):
        pass
    @staticmethod
    def d(a,b):
        pass

def ab(a,b):
    return A().b(a,b)

def ac(a,b):
    return A.c(a,b)

def ad(a,b):
    return A.d(a,b)

class MyTestCase(unittest.TestCase):
    @patch(__name__+".A", autospec=True)
    def test_something(self,mock_a):
        ab(1,2)
        mock_a.return_value.b.assert_called_with(1,2)
        ac(1,2)
        self.assertFalse(mock_a.return_value.c.called)
        mock_a.c.assert_called_with(1,2)
        ad(1,2)
        self.assertFalse(mock_a.return_value.d.called)
        mock_a.d.assert_called_with(1,2)