首页 > 解决方案 > 如何使用 pytest-mock 有条件地模拟函数

问题描述

我有一门课foo.py,我想测试:

import requests

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

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

这是我的测试文件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' object has no attribute 'test_foo.mocked_requests_get'

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

标签: pythonpytestpytest-mock

解决方案


你的方法有点太复杂了——你可以只使用一个标准的模拟,而不需要实现你自己的模拟类。像这样的东西应该工作:

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(请参阅修补位置)。

更新:
如果您需要响应依赖于 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

推荐阅读