如何正确模拟 class 依赖项?

How correctly mock class dependencies?

我最近开始学习如何使用 unittest 库中的 mock,但在弄清楚如何正确模拟 class 依赖项时遇到了问题。 下面是我试图模拟的例子

client.py

class HttpClient:

    def request(self, method, url, params = None):
        if method == "GET":
            return requests.get(url)
        elif method == "POST":
            return requests.post(url, body=params)

这里我将HttpClient对象注入到Postclass

据我所知,我需要模拟 self.client.request,这可以用 requests get 或其他方式替换吗?

data.py

class Post:

    def __init__(self, client: HttpClient):
        self.client = client
        self.base_url = "https://jsonplaceholder.typicode.com"

    def get_posts(self, amount):
        response = self.client.request(method="GET", url=f"{self.base_url}/posts/{amount}")

        if response.ok:
            return response.json()

        return response.status_code

现在是测试部分

test_data.py

class TestPost(unittest.TestCase):

    @patch('app.client.HttpClient')
    def setUp(self, module):
        self.mock_http = MagicMock(autospec=HttpClient)
        self.mock_post = Post(self.mock_http)

    @patch.object(requests, 'get')
    def test_get_posts(self, mock_data):
        mock_data.return_value = {
            'postId': 1,
            'title': 'My title',
            'description': 'Post description'
        }

        response = self.mock_post.get_posts(1)

        assert response['postId'] == 1

当我设置 mock_data.return_value 时,它实际上是否替换了此处调用的原始响应 response = self.mock_post.get_posts(1) 或不是?

也许有人可以解释这是如何工作的

谢谢!

因为你使用了依赖注入,所以你不需要使用mock.patch()。只需创建模拟的 HttpClient 对象并将其传递给 Post class。如果你的模块依赖于一些通过 import 关键字导入的模块,那么你需要使用 mock.patch() 东西来模拟它们。

此外,您可以为 client.request() 方法创建模拟 Response。我们可以使用 requests 包中的 Response class。

data.py:

from client import HttpClient


class Post:

    def __init__(self, client: HttpClient):
        self.client = client
        self.base_url = "https://jsonplaceholder.typicode.com"

    def get_posts(self, amount):
        response = self.client.request(method="GET", url=f"{self.base_url}/posts/{amount}")

        if response.ok:
            return response.json()

        return response.status_code

test_data.py:

import unittest
from requests import Response
from unittest.mock import MagicMock, Mock
from client import HttpClient
from data import Post


class TestPost(unittest.TestCase):

    def setUp(self):
        self.mock_http = MagicMock(autospec=HttpClient)
        self.mock_post = Post(self.mock_http)

    def test_get_posts(self):

        mock_response = Mock(spec=Response)
        mock_response.json.return_value = {
            'postId': 1,
            'title': 'My title',
            'description': 'Post description'
        }
        self.mock_http.request.return_value = mock_response

        response = self.mock_post.get_posts(1)
        self.mock_http.request.assert_called_once_with(method="GET", url="https://jsonplaceholder.typicode.com/posts/1")
        assert response['postId'] == 1


if __name__ == '__main__':
    unittest.main(verbosity=2)

测试结果:

test_get_posts (__main__.TestPost) ... ok

----------------------------------------------------------------------
Ran 1 test in 0.002s

OK
Name                                      Stmts   Miss  Cover   Missing
-----------------------------------------------------------------------
src/Whosebug/69751753/client.py          7      4    43%   6-9
src/Whosebug/69751753/data.py           10      1    90%   16
src/Whosebug/69751753/test_data.py      18      0   100%
-----------------------------------------------------------------------
TOTAL                                        35      5    86%