How correctly mock class dependencies?

Viewed 57

I've recently started learning how to use mock from unittest library and encountered a problem figuring out how to correctly mock class dependencies. Below is the example i'm trying to mock

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)

Here I inject the HttpClient object into the Post class

As I understood I need to mock self.client.request and this can be replaces with requests get or maybe in some other way?

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

And now the testing part

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

So does when I set mock_data.return_value it actually replace the original response that is being called here response = self.mock_post.get_posts(1) or not?

Maybe someone could explain how this exactly works

Thank You!

1 Answers

Since you use dependency injection, you don't need to use mock.patch(). Just create mocked HttpClient object and pass it to the Post class. If your module depends on some modules which imported by import keyword, then you need to use mock.patch() things to mock them.

Besides, you can create mocked Response for the client.request() method. We can use the Response class from the requests package.

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 result:

test_get_posts (__main__.TestPost) ... ok

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

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