在 Pytest 中使用 MagicMock 对象时如何检查嵌套函数中的异常?

问题描述

我有一个函数 (myfunc),带有验证输入 ac 凭据,用于设置调用 func_z 的服务。

对于某些验证输入,func_z 会抛出错误,而在其他情况下,它会返回一些值的字典。我有一个 outer_func 可以修改 func_z输出

我试图这样模拟 func_z

class XError(Excception):
    pass

def myfunc(a,b,c):
    x = c.setup_client('x')
    try:
        x.func_z(a) # Check if x.func_z(a) works proper
    except:
        raise XError
    
    return b**2


def outer_func(a,c):
    return myfunc(a,c) + 1

在测试该函数时,我不得不模拟 c 凭据。所以我尝试测试:

import pytest
from unittest.mock import Magicmock


test_data = ( (('fr',5),26),('de',7,50),(('zh',XError) )

SIDE_EFFECTS = {'fr': {'status': 'okay'},'de': {'status': 'okay'},'zh': XError}

@pytest.mark.parametrize("a,b",test_data)
def mytest(a,expected):
    mock_creds = Magicmock()
    mock_service = Magicmock()
    mock_creds.setup_client.return_value = mock_service
    mock_service.func_z.return_value = SIDE_EFFECTS[a]
    
    assert outer_func(a,mock_creds) == expected
    

不知何故,在 pytest 中没有引发 XError,对于 26 输入,输出返回 ('zh',5) 而不是 XError。

但好像我没有在嘲笑任何东西。

我是否错误地使用了模拟对象中的返回值?

是否可以允许和检查在 pytest 中使用模拟对象引发的错误输出

解决方法

有问题的测试有两个问题。

  1. 在模拟中返回异常:

    mock_service.func_z.return_value = XError
    

    实际上是一个

    def func_z():
        return XError
    

    这肯定不是你想要的。相反,您希望 func_z 模拟提出一个错误;为此,您需要使用 side_effect:

    mock_service.func_z.side_effect = XError
    
  2. 断言异常是从测试函数返回的:outer_func 不会返回异常,而是引发异常,所以

    assert outer_func(a,b,mock_creds) == expected
    

    对于 (('zh',5),XError) 参数将失败,因为 outer_func 不会返回。

相反,编写两个单独的测试,因为代码可以在 myfunc 中采用两条不同的路径; test_happy 无一例外地覆盖了路径,出现错误的路径由 test_xerror 覆盖。两个测试(组装 mock_service)共有的代码移出到一个装置中。

@pytest.fixture
def mock_creds():
    mock_creds = MagicMock()
    mock_service = MagicMock()
    mock_creds.setup_client.return_value = mock_service
    return mock_creds


happy_data = (('fr',5,26),('de',7,50))
SIDE_EFFECTS = {'fr': {'status': 'okay'},'de': {'status': 'okay'}}

@pytest.mark.parametrize("a,expected",happy_data)
def test_happy(mock_creds,a,expected):
    func_z_return = SIDE_EFFECTS[a]
    mock_creds.setup_client.return_value.func_z.return_value = func_z_return
    assert outer_func(a,mock_creds) == expected


xerror_data = (('zh',)

@pytest.mark.parametrize("a,b",xerror_data)
def test_xerror(mock_creds,b):
    mock_creds.setup_client.return_value.func_z.side_effect = XError
    with pytest.raises(XError):
        outer_func(a,mock_creds)

请注意 pytest.raises 上下文如何用于测试 XError 是否在上次测试中引发。如果要测试异常详细信息,可以存储引发的异常并在 with 块之后检查它:

with pytest.raises(XError) as excinfo:
    outer_func(a,mock_creds)
ex = excinfo.value
assert isinstance(ex,XError)  # well,duh
assert ex.message == "An XError message if passed"  # etc.