如何模拟依赖 class 的方法?

How to mock a method of a dependent class?

我正在尝试测试一个方法 (Creator.do_a_call),该方法调用另一个 class 的方法 (Requester.make_request),该方法进行外部调用(所有代码都在下面)。所以我想模拟 Request.make_request 到 return 一个模拟的 response 对象,这样它就不会进行任何外部调用,但是当我这样做时它会给我一个错误(见下文)

代码如下:

Requester.py:

from requests import Response, Session, Request
class Requester:
    url: str = ''

    def __init__(self, url: str):
        self.url = url

    def make_request(self, path: str, method: str = 'get', body: dict = None, custom_headers: dict = None) -> Response:
        print("make_request")
        url = self.url + path
        session = Session()
        request = Request(method, url, json=body)
        response = session.send(request.prepare())
        return response

Creator.py

from Requester import Requester
from http import HTTPStatus as status
from requests import Response

class Creator:
    def do_a_call(self, url) -> Response:
        print("do_a_call")
        requester = Requester(url)
        response = requester.make_request(
                    "/",
                    "GET"
                )
        print(type(response))
        if response.status_code != status.OK and response.status_code != status.ACCEPTED:
            raise Exception(response.status_code, response.text)
        return response

main.py

from Creator import Creator

print("main")
creator = Creator()
response = creator.do_a_call("https://google.com")
print(response)

test.py

import unittest
from unittest.mock import patch
from Creator import Creator
from requests import Session
import requests_mock
class CreatorTest(unittest.TestCase):

    def mocked_make_request(self, two, three):
        session = Session()
        mocker = requests_mock.Mocker(session=session)
        adapter = requests_mock.Adapter()
        adapter.register_uri('POST', 'mock://test.com', text="Invalid policy number supplied", status_code=400)
        return mocker.post("mock://test.com", status_code=400)

    @patch('Requester.Requester.make_request')
    def test_do_a_call_side_effect(self, make_request):
        creator = Creator()
        make_request.side_effect = self.mocked_make_request
        response = creator.do_a_call("mock://test.com")
        self.assertEqual(200, response.status_code)

    @patch('Requester.Requester.make_request')
    def test_do_a_call_return_value(self, make_request):
        creator = Creator()
        make_request.return_value = self.mocked_make_request("", "")
        response = creator.do_a_call("mock://test.com")
        self.assertEqual(200, response.status_code)

        
if __name__ == "__main__":
    unittest.main()

在我的研究中,我发现 应该 工作的两种方式 - 更改模拟的 return_value,或更改模拟的 side_effect ,这两个都给我同样的错误:

Traceback (most recent call last):
  File "/Library/Developer/CommandLineTools/Library/Frameworks/Python3.framework/Versions/3.8/lib/python3.8/unittest/mock.py", line 1325, in patched
    return func(*newargs, **newkeywargs)
  File "test.py", line 19, in test_do_a_call_side_effect
    response = creator.do_a_call("mock://test.com")
  File "/Users/citrja/Documents/git/python_test/Creator.py", line 14, in do_a_call
    if response.status_code != status.OK and response.status_code != status.ACCEPTED:
AttributeError: '_Matcher' object has no attribute 'status_code'

我发现问题了!我没有将适配器连接到会话,我以为这是自动发生的,但显然不是。这是更新后的 mocked_make_request 函数:

def mocked_make_request(self, two, three):
        session = Session()
        mocker = requests_mock.Mocker(session=session)
        adapter = requests_mock.Adapter()
        adapter.register_uri('GET', 'mock://test.com', text="Invalid policy number supplied", status_code=400)
        session.mount("mock://", adapter)
        return session.get("mock://test.com")