模拟一个函数抛出异常以测试一个except块。

180

我有一个叫做 foo 的函数,它调用了另一个函数 bar。如果调用 bar() 引发了 HttpError 异常,并且 HTTP 响应状态码为 404,则我想对其进行特殊处理,否则重新引发该异常。

我正尝试编写一些针对 foo 函数的单元测试,并模拟调用 bar()。不幸的是,我无法让模拟调用 bar() 引发被我的 except 块捕获到的异常。

以下是展示我的问题的代码:

import unittest
import mock
from apiclient.errors import HttpError


class FooTests(unittest.TestCase):
    @mock.patch('my_tests.bar')
    def test_foo_shouldReturnResultOfBar_whenBarSucceeds(self, barMock):
        barMock.return_value = True
        result = foo()
        self.assertTrue(result)  # passes

    @mock.patch('my_tests.bar')
    def test_foo_shouldReturnNone_whenBarRaiseHttpError404(self, barMock):
        barMock.side_effect = HttpError(mock.Mock(return_value={'status': 404}), 'not found')
        result = foo()
        self.assertIsNone(result)  # fails, test raises HttpError

    @mock.patch('my_tests.bar')
    def test_foo_shouldRaiseHttpError_whenBarRaiseHttpErrorNot404(self, barMock):
        barMock.side_effect = HttpError(mock.Mock(return_value={'status': 500}), 'error')
        with self.assertRaises(HttpError):  # passes
            foo()

def foo():
    try:
        result = bar()
        return result
    except HttpError as error:
        if error.resp.status == 404:
            print '404 - %s' % error.message
            return None
        raise

def bar():
    raise NotImplementedError()

我遵循Mock文档所说的设置Mock实例的side_effectException类,以使模拟函数引发错误。

我还查看了一些其他相关的StackOverflow Q&As,并且看起来我正在做他们用来引发mock异常的相同操作。

为什么设置barMockside_effect没有引发预期的Exception?如果我做了一些奇怪的东西,那么该如何测试except块中的逻辑?


我非常确定你的异常已经被触发了,但我不确定你是如何设置resp.status代码的。HTTPError是从哪里来的? - Martijn Pieters
@MartijnPieters HttpError是在Google的apiclient中定义的一个类,我们在GAE中使用它。它的__init__是用参数(resp, content)定义的,因此我试图为响应创建一个模拟实例,并指定适当的状态代码。 - Jesse Webb
好的,所以它是这个类;但你不需要使用return_value;因为resp没有被调用 - Martijn Pieters
1
我再次尝试了我的代码,这次没有使用 HttpError,而是使用普通的 Exception 实例。这样做完美地解决了问题。这意味着问题肯定与我配置 HttpError 实例有关,可能与我为响应创建 Mock 实例的方式有关。 - Jesse Webb
1个回答

210

你的模拟对象已经成功地引发了异常,但是error.resp.status的值缺失。与其使用return_value,不如告诉Mock status是一个属性:


barMock.side_effect = HttpError(mock.Mock(status=404), 'not found')
< p > Mock() 的附加关键字参数将被设置为生成的对象的属性。

我将您的foobar定义放在一个名为my_tests的模块中,并添加了HttpError,以便我也可以使用它,然后您的测试就可以顺利运行:

>>> from my_tests import foo, HttpError
>>> import mock
>>> with mock.patch('my_tests.bar') as barMock:
...     barMock.side_effect = HttpError(mock.Mock(status=404), 'not found')
...     result = my_test.foo()
... 
404 - 
>>> result is None
True

您甚至可以看到print '404 - %s' % error.message这一行被执行了,但我认为您想在那里使用error.content代替;不管怎样,那是从第二个参数设置的HttpError()属性。


10
"side_effect" 是关键部分。 - Daniel Butler
HttpError(mock.Mock(status=404), 'not found')是什么意思?对我来说,它没有将状态码设置为404,它仍然是None。 - Yang
1
@Yang:你是否使用了google-api-client异常类?如果没有提供具体信息,我无法帮助你。mock.Mock(status=404)表达式创建了一个模拟对象,该对象具有.status属性,HttpError将其存储为resp属性。在问题中,它被访问为error.resp.status - Martijn Pieters

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接