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!
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%