如何使用 pytest-mock 来监视函数

问题描述

我想测试 foo.py 中的一个类:

import requests

class Foo:
    def fooMethod(self,url):
        response = requests.get(url)
        return response

我想替换请求调用来模拟响应。

这是我在 test_foo.py 中的测试文件

from foo import Foo

def mocked_requests_get(*args,**kwargs):
    class MockResponse:
        def __init__(self,text,code):
            self.text = text
            self.code = code
    
    if args[0] == "http://localhost":
        return MockResponse("Test response",200)

    return MockResponse(None,404)


class TestFoo:
    def test_foo(self,mocker):
        a = Foo()
        mocker.patch ('foo.requests.get',mocked_requests_get)
        spy = mocker.spy (a,'test_foo.mocked_requests_get')
        response = a.fooMethod("http://localhost")
        assert response.text == "Test response"
        assert spy.call_count == 1

我想检查 mocked_requests_get 函数是否只被调用一次。

解释器在 spy = mocker.spy ... 行给出错误: 'Foo' 对象没有属性 'test_foo.mocked_requests_get'

这是可以理解的 - 但我无法找到一种方法来访问引用该函数的对象实例。请问有人可以帮忙吗?

解决方法

您的方法有点太复杂了 - 您可以只使用标准模拟,而无需实现自己的模拟类。这样的事情应该可以工作:

class TestFoo:
    def test_foo(self,mocker):
        a = Foo()
        get_mock = mocker.patch('requests.get')
        get_mock.return_value.text = "Test response"
        response = a.fooMethod()
        assert response.text == "Test response"
        assert get_mock.call_count == 1

另请注意,您必须修补 requests.get 而不是 foo.requests.get,因为您直接导入 requests(请参阅 where to patch)。

更新:
如果您需要响应依赖于 url,如更新的问题所示,您可以使用 side_effect,它可以采用函数或类对象(除非 return_value,它需要一个评估值):

class MockedResponse:
    def __init__(self,url):
        self.url = url

    @property
    def text(self):
        responses = {"url1": "response1","url2": "response2","url3": "response3"}
        if self.url in responses:
            return responses[self.url]
        return "default"


class TestFoo:
    def test_foo(self,mocker):
        a = Foo()
        get_mock = mocker.patch('requests.get')
        get_mock.side_effect = MockedResponse
        response = a.fooMethod("url2")
        assert response.text == "response2"
        assert get_mock.call_count == 1