How to mock requests using pytest? [duplicate] - python

This question already has answers here:
How can I mock requests and the response?
(20 answers)
Closed 2 years ago.
I'm writing some unit test code and I would like to mock requests module that is being used inside my function:
import requests
def get_employee(id):
resp = requests.get(f'{__BASE_URL}/employee/{id}')
if resp.status_code == 404:
return None
return resp.json()
I've tried to mock it using this code:
def test_get_employee(mocker):
get_request_mock = mocker.patch.object(get_employee, "resp")
print(get_request_mock)
get_request_mock.status_code = 200
get_request_mock.json.return_value = {'name': 'awesome-mock'}
resp = get_employee('random-id')
assert resp == {'name': 'awesome-mock'}
How can I mock requests using mocker? Is possible?

You can use requests-mock (PyPI), there is a fixture for a pytest usage.
For your example:
from correct.package import __BASE_URL
from requests import HTTPError
def test_get_employee(requests_mock):
test_id = 'random-id'
requests_mock.get(f'{__BASE_URL}/employee/{test_id}', json= {'name': 'awesome-mock'})
resp = get_employee('random-id')
assert resp == {'name': 'awesome-mock'}
def test_absent_employee(requests_mock):
test_id = 'does_not_exist'
requests_mock.get(f'{__BASE_URL}/employee/{test_id}', status_code=404)
with pytest.raises(HTTPError):
resp = get_employee(test_id)

This may help
from unittest import TestCase
import requests
import requests_mock
class TestHTTPRequest(TestCase):
def test_context_manager(self):
with requests_mock.Mocker() as mock_request:
mock_request.get("http://123-fake-api.com", text="Hello!")
response = requests.get("http://123-fake-api.com")
assert response.text == "Hello!"

Related

python how to mock 3rd party library response

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

Mocking urllib3.PoolManager().request function with python

I have a function that makes a POST request using urllib3.PoolManager(). Now in the unit test I want to mock said request but having some difficulty. What's the correct way to do it?
My code:
http = urllib3.PoolManager()
# my func
def func(event, context):
...
resp = http.request('POST', url, body=encoded_msg)
...
# unit test
#patch('urllib3.PoolManager.request')
def test_lambda_handler(self, mock_instance):
mock_instance.return_value = Mock(status = "200", data = "success")
res = func(event, [])
mock_instance.request.assert_called_once()
I get this error "AssertionError: Expected 'request' to have been called once. Called 0 times."
Since you call the constructor of the urllib3.PoolManager class in global (or module) scope. Reference: #where-to-patch
module b does import a and some_function uses a.SomeClass. ... In this case the class we want to patch is being looked up in the module and so we have to patch a.SomeClass instead: #patch('a.SomeClass')
Option 1: import the func after mocking urllib3.PoolManager class
lambda_handler.py:
import urllib3
import json
http = urllib3.PoolManager()
print('http: ', http)
def func(event, context):
url = 'http://localhost:3000'
data = {'attribute': 'value'}
encoded_msg = json.dumps(data).encode('utf-8')
resp = http.request('POST', url, body=encoded_msg)
test_lambda_handler.py:
import unittest
import json
from unittest import mock
class TestLambdaHandler(unittest.TestCase):
#mock.patch('urllib3.PoolManager')
def test_lambda_handler(self, mock_PoolManager):
from lambda_handler import func
mock_http = mock_PoolManager.return_value
event = {}
func(event, [])
mock_http.request.assert_called_once_with('POST', 'http://localhost:3000', body=json.dumps({'attribute': 'value'}).encode('utf-8'))
unittest.main(verbosity=2)
test result:
test_lambda_handler (__main__.TestLambdaHandler) ... http: <MagicMock name='PoolManager()' id='4475122928'>
ok
----------------------------------------------------------------------
Ran 1 test in 0.063s
OK
Name Stmts Miss Cover Missing
---------------------------------------------------------------------------------
src/stackoverflow/69890084/lambda_handler.py 9 0 100%
src/stackoverflow/69890084/test_lambda_handler.py 12 0 100%
---------------------------------------------------------------------------------
TOTAL 21 0 100%
Option 2: mock the global variable http, you don't need to import the func after mocking
If you import the lambda_handler module at the top of the test file, the module lambda_handler will have a reference to the real urllib3.PoolManager so that it's too late for mocking.
import unittest
import json
from unittest import mock
from lambda_handler import func
class TestLambdaHandler(unittest.TestCase):
#mock.patch('lambda_handler.http')
def test_lambda_handler(self, mock_http):
event = {}
func(event, [])
mock_http.request.assert_called_once_with('POST', 'http://localhost:3000', body=json.dumps({'attribute': 'value'}).encode('utf-8'))
unittest.main(verbosity=2)
test result:
http: <urllib3.poolmanager.PoolManager object at 0x1104b2610>
test_lambda_handler (__main__.TestLambdaHandler) ... ok
----------------------------------------------------------------------
Ran 1 test in 0.002s
OK
Name Stmts Miss Cover Missing
---------------------------------------------------------------------------------
src/stackoverflow/69890084/lambda_handler.py 9 0 100%
src/stackoverflow/69890084/test_lambda_handler.py 11 0 100%
---------------------------------------------------------------------------------
TOTAL 20 0 100%

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.

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