python how to mock 3rd party library response - python

I'm using a 3rd party library "mailjet" to send email.
Here's the doc: https://dev.mailjet.com/email/guides/getting-started/#prerequisites
This is the method in send_email.py that I want to test:
from mailjet_rest import Client
def send_email(email_content):
client = Client(auth=(api_key, api_secret))
response = client.send.create(data=email_content)
if response.status_code != 200:
// raise the error
else:
print(response.status_code)
return response.status_code
...
I wanted to mock the status code. This is what I tried in test_send_email.py:
from unittest.mock import MagicMock, patch
import unittest
import send_email
class TestClass(unittest.TestCase):
#patch("send_email.Client")
def test_send_email(self, _mock):
email_content = {...}
_mock.return_value.status_code = 200
response = send_email(email_content)
// assert response == 200
When I run the test, it didn't print out the status code, it prints:
<MagicMock name='Client().send.create().status_code' id='140640470579328'>
How can I mock the status_code in the test? Thanks in advance!!!!!

You are putting the attribute in the wrong place.
You mock the Client, which is correct, and put something in its return_value, which is also good.
However, the line
response = client.send.create(data=email_content)
applies on the return_value of your mock (client), the send attribute and its create method.
So what you need is to set the return_value.status_code of this create method.
class TestClass(unittest.TestCase):
#patch("send_email.Client")
def test_send_email(self, mock_client):
email_content = {...}
# this puts 200 in Client().status_code
# mock_client.return_value.status_code = 200
# that puts 200 in Client().send.create().status_code
mock_client.return_value.send.create.return_value.status_code = 20
response = send_email(email_content)
assert response == 200

Related

How to test response of service using mock?

I have the following service with a post request:
import requests
class Attt():
def get_req(self):
pload = {'username': 'Olivia', 'password': '123'}
r = requests.post('https://httpbin.org/post', data=pload)
print(r.text)
print(r.content)
obj=Attt()
obj.get_req()
I would like to mock the request so that I can create the needed response of the service.
I have written a test like below:
import unittest
from unittest.mock import patch
from mock_tutorial.attt import Attt
import pandas as pd
class TestMockService(unittest.TestCase):
def test_mock(self):
fake_json = [{'test': "mock"}]
with patch('mock_tutorial.attt.requests.post') as mock_get:
mock_get.return_value.status_code = 200
mock_get.return_value.json.return_value = fake_json
obj = Attt()
response = obj.get_req()
print('response json', response.json())
self.assertEqual(response.status_code, 200)
self.assertEqual(response.json(), fake_json)
if __name__ == "__main__":
unittest.main()
But as the get_req method doesn't return the reesponse, the assertions fail: AttributeError: 'NoneType' object has no attribute 'json'. How can I modify the assertions to check that the response of the get_req method has been mocked correspondingly, to the fake_json variable?
It is because you are not returning anything from Attt.get_req thus it returns None. So in your test, it is like calling None.json(). You should return the response of the request:
...
class Attt():
def get_req(self):
...
return r
...
Also as documented, you should wrap your code in attt.py so that it doesn't get executed when imported:
if __name__ == "__main__":
obj=Attt()
obj.get_req()
This seems fine since this is just for tutorial. But take note that there are already libraries you can use that mocks the requests module such as requests-mock.
Update
If the method wouldn't return anything, then all we can do to check is by adding a spy to the target functionality. But take note that this wouldn't make sense in reality because what we are spying on is a mocked/patched functionality which is requests.post. This would only be good for learning purposes but in reality, better yet not add the test as it doesn't add any value at all. We can use pytest-mock for this purpose:
mock_tutorial/attt.py
import requests
class Attt():
def get_req(self):
pload = {'username': 'Olivia', 'password': '123'}
r = requests.post('https://httpbin.org/post', data=pload)
print(r.text)
print(r.content)
test_attt.py
from unittest.mock import patch
from mock_tutorial import attt
from mock_tutorial.attt import Attt
import pytest
def test_mock(mocker): # Run <pip install pytest-mock>
fake_json = [{'test': "mock"}]
with patch('mock_tutorial.attt.requests.post') as mock_get:
mock_get.return_value.status_code = 200
mock_get.return_value.json.return_value = fake_json
post_spy = mocker.spy(attt.requests, "post")
obj = Attt()
response = obj.get_req()
assert post_spy.spy_return.status_code == 200
assert post_spy.spy_return.json() == fake_json

Flask unit-test: Unable to mock a function in a post request

I have the following flask app.
# app.py
from flask import Flask, request
from predict import query_sku
app = Flask(__name__)
#app.route("/predict", methods=["POST"])
def predict():
content = request.json
max_results = content["resultSize"]
input_sku_list = content["sku"]
skus = query_sku(input_sku_list, max_results)
return {"sku": skus}
if __name__ == "__main__":
app.run()
I wrote a unit-test for it using pytest and tried to mock the query_sku function using unittest.mock.
import sys
from unittest.mock import Mock
import pytest
import app
def test_api_mocked_model():
sys.modules["predict"] = Mock()
from predict import query_sku
query_sku.return_value = "dummy"
with app.app.test_client() as client:
response = client.post('/predict', json={"resultSize":10,"sku": "x"}).json
assert response == {"sku": "dummy"}
del sys.modules['predict']
But I was unable to mock that function within the request. It just gave the following assertion error.
> assert response == {"sku": "dummy"}
E AssertionError: assert None == {'sku': 'dummy'}
E +None
E -{'sku': 'dummy'}
tests/unit_tests/test_api.py:34: AssertionError
How can I get it to work?
[EDIT]
I added in the query_sku function below. Intentionally return a value that is different from the Mock function return_value.
# predict.py
def query_sku(input_sku_list, topn):
return "actual function"
But the unit-test is still querying from the actual function, as shown below.
assert response == {"sku": "dummy"}
E AssertionError: assert {'sku': 'actual function'} == {'sku': 'dummy'}
E Differing items:
E {'sku': 'actual function'} != {'sku': 'dummy'}
from unittest.mock import MagicMock
def test_api_mocked_model():
## sys.modules["predict"] = Mock() ## why sys module ?
from predict import query_sku
query_sku = MagicMock(return_value="dummy") # mock directly
with app.app.test_client() as client:
response = client.post('/predict', json={"resultSize":10,"sku": "x"}).json
assert response == {"sku": "dummy"}
del sys.modules['predict']
Could you try this code ?
Actually, the answer above did not work for me. Rather than using MagicMock, I used #mock.patch and that worked. And it is less complicated too. For example, in your app.py, you can have an API endpoint that you want to stub out with a mock:
app.py
def fetch_nodes(usr, passwd, hostname, node_name):
sftp_connection = get_sftp_connection(usr, passwd, hostname)
nodes = sftp_connection.listdir_attr(node_name)
return nodes
Now in test_app.py, you can stub it out like this:
test_app.py
from app import app
import unittest
from unittest import mock
class FlaskTest(unittest.TestCase):
#mock.patch('app.fetch_nodes')
def test_list_child_nodes(self, mocked):
tester = app.test_client(self)
mocked.return_value = []
response = tester.post("/api/listchildnodes", json={
'usr': 'test',
'jwt': 'test',
'node_name': 'test',
})
statuscode = response.status_code
self.assertEqual(statuscode, 200)
if __name__ == "__main__":
unittest.main()
The important thing is the definition of mock.patch('app.fetch_nodes') which references the fetch_nodes function in app.py and secondly it is passed to the test function as "mocked". Then we set the return value of "mocked". And finally call the flask endpoint. Now the flask endpoint will use the mock instead of actually hitting an sftp server.

Python Mock: Result of method call is __getitem__ instead of actual value

I have a unit test and mocking an external call. Here's the abbreviated code for the function I'm trying to test in service.py file
def post_data()
req = request.Request()
response = req.post(payload, url, json.dumps({"data": kwargs['data']}))
if response['request']['status'] == 'SUCCESS' and response['data']:
run_id = response.json()['data']['run_id']
response = track_run_to_completion(run_id, **kwargs)
return response
Here's my unit test method
#patch('service.request.Request.post')
def test_post_data(self, mock_post):
kwargs = {'a':'abc'}
expected = json.dumps({'request':{'status':'ERROR'},'data':{}})
mock_post.return_value = MagicMock(status_code=200, response=expected)
mock_post.assert_called_once_with({'action': 'trigger'}, 'a/abc', '{"data": {}}') # SUCCESS!
result = service.post_data(**kwargs)
print result
When I print the result, I was expecting to see the json, but get <MagicMock name='post()' id='4488707600'>. What am I missing here? I'm new to Python and started writing unit tests for an existing application.

How do I set different value for my request mock in each different test?

In my unit test I have the following setUp function:
#patch("FrontEnd.FrontEnd.get_cname")
def setUp(self, mock_get_cname):
mock_requests = MagicMock()
mock_requests.return_value.json.side_effect = return_side_effect
mock_requests.return_value.status_code = 200
mock_requests.return_value.text = 'ssl'
self.requests = patch.object(requests, 'get', mock_requests)
self.requests.start()
In one test I have the following:
#patch("requests.get")
def test_search_resource(self, mock_get):
"""Test for _search_resource"""
mock_get.return_value.status_code = 200
self.fe._search_resource("hulahoop")
self.assertTrue(mock_get.call_args,
call('www.example.com/cdn_resources.json?q=hulahoop',
auth=('user', 'password'),
timeout=5))
mock_get.reset_mock()
mock_get.return_value.status_code = 400
with self.assertRaises(Exception):
self.fe._search_resource("hulahoop")
I am repeating the mock_get here although it has been patched in setUp, for the sole reason of being able to assign it different status_code. How do I assign different values to my status_code without patching requests.get again ?

Django unittest and mocking the requests module

I am new to Mock and am writing a unit test for this function:
# utils.py
import requests
def some_function(user):
payload = {'Email': user.email}
url = 'http://api.example.com'
response = requests.get(url, params=payload)
if response.status_code == 200:
return response.json()
else:
return None
I am using Michael Foord's Mock library as part of my unit test and am having difficulty mocking the response.json() to return a json structure. Here is my unit test:
# tests.py
from .utils import some_function
class UtilsTestCase(unittest.TestCase):
def test_some_function(self):
with patch('utils.requests') as mock_requests:
mock_requests.get.return_value.status_code = 200
mock_requests.get.return_value.content = '{"UserId":"123456"}'
results = some_function(self.user)
self.assertEqual(results['UserId'], '123456')
I have tried numerous combinations of different mock settings after reading the docs with no luck. If I print the results in my unit test it always displays the following instead of the json data structure I want:
<MagicMock name=u'requests.get().json().__getitem__().__getitem__()' id='30315152'>
Thoughts on what I am doing wrong?
Patch json method instead of content. (content is not used in some_function)
Try following code.
import unittest
from mock import Mock, patch
import utils
class UtilsTestCase(unittest.TestCase):
def test_some_function(self):
user = self.user = Mock()
user.email = 'user#example.com'
with patch('utils.requests') as mock_requests:
mock_requests.get.return_value = mock_response = Mock()
mock_response.status_code = 200
mock_response.json.return_value = {"UserId":"123456"}
results = utils.some_function(self.user)
self.assertEqual(results['UserId'], '123456')
Another pattern I like to use that is a little more reusable would be to start the patcher in your unit test's setUp method. It's also important to check that mock request was called with the expected parameters:
class UtilsTestCase(TestCase):
def setUp(self):
self.user = Mock(id=123, email='foo#bar.com')
patcher = patch('utils.requests.get')
self.mock_response = Mock(status_code=200)
self.mock_response.raise_for_status.return_value = None
self.mock_response.json.return_value = {'UserId': self.user.id}
self.mock_request = patcher.start()
self.mock_request.return_value = self.mock_response
def tearDown(self):
self.mock_request.stop()
def test_request(self):
results = utils.some_function(self.user)
self.assertEqual(results['UserId'], 123)
self.mock_request.assert_called_once_with(
'http://api.example.com'
payload={'Email': self.user.email},
)
def test_bad_request(self):
# override defaults and reassign
self.mock_response.status_code = 500
self.mock_request.return_value = self.mock_response
results = utils.some_function(self.user)
self.assertEqual(results, None)
self.mock_request.assert_called_once_with(
'http://api.example.com'
payload={'Email': user.email},
)
Another way that I believe is more clear and straight forward:
import unittest
from mock import Mock, patch
import utils
class UtilsTestCase(unittest.TestCase):
def test_some_function(self):
mock_response = Mock()
mock_response.status_code = 200
mock_response.json.return_value = {"UserId": "123456"}
with patch('utils.requests.get') as mock_requests:
results = utils.some_function(self.user)
self.assertEqual(results['UserId'], '123456')

Categories

Resources