I'm a newbie to unit testing and mock. I have one script get_table.py and a function in it
def get_table_name(id):
url = "https://some_api" + id
table = requests.get(url)
return table
then I created a unit testing script as follows:
from get_table import get_table_name
from unittest.mock import patch
class TestFetchTable(unittest.TestCase):
#patch('get_table.get_table_name')
def test_get_table_name(self,mock):
mock.return_value = 'table_1'
result = get_table_name('id1')
self.assertEquals(result, 'table_1')
if __name__== '__main__':
unittest.main()
The unit test is passed here but the test still ran over the actual API not the mocked API. May I know that I did wrong here?
You should mock the requests.get method.
E.g.
get_table.py:
import requests
def get_table_name(id):
url = "https://some_api" + id
table = requests.get(url)
return table
test_get_table.py:
from get_table import get_table_name
from unittest.mock import patch
import unittest
class TestFetchTable(unittest.TestCase):
#patch('get_table.requests')
def test_get_table_name(self, mock_requests):
mock_requests.get.return_value = 'table_1'
result = get_table_name('id1')
self.assertEqual(result, 'table_1')
if __name__ == '__main__':
unittest.main()
unit test result with coverage report:
.
----------------------------------------------------------------------
Ran 1 test in 0.001s
OK
Name Stmts Miss Cover Missing
----------------------------------------------------------------------------
src/stackoverflow/63306217/get_table.py 5 0 100%
src/stackoverflow/63306217/test_get_table.py 10 0 100%
----------------------------------------------------------------------------
TOTAL 15 0 100%
Related
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%
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.
I am trying to write test case, I want to mock data object returned from MongoClient(), below is the code.
numbers.py
def get_count():
client_int = MongoClient('abc.xyz.com', port=27010)
return client_int
test_numbers.py
#patch('pymongo.MongoClient')
def test_get_count(mocked_object):
mocked_object.return_value = [{'1': 'data'}]
assert numbers.get_count() == [{'1': 'data'}] # Here i am getting Assertion Error, MongoClient!=[{'1': 'data'}]
How to make this work?? What went wrong??
First of all, you should rename your module. You can't use numbers, because it conflicts with python built-in library numbers.
You didn't patch the target correctly. You should patch MongoClient of my_numbers.py module. For more info, see where-to-patch
E.g.
my_numbers.py:
from pymongo import MongoClient
def get_count():
client_int = MongoClient('abc.xyz.com', port=27010)
return client_int
test_my_numbers.py:
import unittest
from unittest.mock import patch
import my_numbers
class TestNumbers(unittest.TestCase):
#patch('my_numbers.MongoClient')
def test_get_count(self, mocked_object):
mocked_object.return_value = [{'1': 'data'}]
assert my_numbers.get_count() == [{'1': 'data'}]
mocked_object.called_once_with_value('abc.xyz.com', port=27010)
if __name__ == '__main__':
unittest.main()
unit test result:
⚡ coverage run /Users/dulin/workspace/github.com/mrdulin/python-codelab/src/stackoverflow/66852436/test_my_numbers.py && coverage report -m --include='./src/**'
.
----------------------------------------------------------------------
Ran 1 test in 0.001s
OK
Name Stmts Miss Cover Missing
-----------------------------------------------------------------------------
src/stackoverflow/66852436/my_numbers.py 4 0 100%
src/stackoverflow/66852436/test_my_numbers.py 11 0 100%
-----------------------------------------------------------------------------
TOTAL 15 0 100%
i have a function in python in athena.py file
#retry(stop_max_attempt_number=10,
wait_exponential_multiplier=300,
wait_exponential_max=1 * 60 * 1000)
def poll_status(_id):
result = client.get_query_execution(QueryExecutionId=_id)
state = result['QueryExecution']['Status']['State']
if state == 'SUCCEEDED':
return result
elif state == 'FAILED':
return result
else:
raise Exception
where client is reference of boto3.client like this:
client = boto3.client('athena', 'us-west-2')
i have written the unit test for this function using unittest in test_athena.py
#mock.patch('boto3.client')
def test_poll_status(self, mock_client):
event1 = {'QueryExecution': {'Status': {'State': 'SUCCEEDED'}}}
instance = mock_client.return_value()
instance.get_query_execution.return_value = event1
result = athena.poll_status('id')
expected_result = event1
self.assertEqual(expected_result, result)
But this fails. I don't know the reason as i wrote test cases for other functions in similar fashion but this one does not work.
"botocore.exceptions.NoCredentialsError: Unable to locate credentials"
This error is thrown
imports in athena.py
import boto3
from retrying import retry
imports in test_athena.py
import unittest
from unittest import mock
Here's a way to make your test pass - or fail fast.
The fail-fast part is because I'm mocking the retry piece.
Then, I'm mocking a specific function of the client object.
I'm attaching a complete file - including the original code, the test, and a 'main'. It passes on my machine.
import boto3
from retrying import retry
import retrying
import unittest
from unittest import mock
client = boto3.client('athena', 'us-west-2')
#retry(stop_max_attempt_number=10,
wait_exponential_multiplier=300,
wait_exponential_max=1 * 60 * 1000)
def poll_status(_id):
result = client.get_query_execution(QueryExecutionId=_id)
state = result['QueryExecution']['Status']['State']
if state == 'SUCCEEDED':
return result
elif state == 'FAILED':
return result
else:
raise Exception
dummy_retry = retrying.Retrying(stop_max_attempt_number = 10, wait_fixed=1)
class MyTests(unittest.TestCase):
#mock.patch('retrying.Retrying', new = lambda **kwargs: dummy_retry)
#mock.patch.object(client, 'get_query_execution')
def test_poll_status(self, mock_client):
event1 = {'QueryExecution': {'Status': {'State': 'SUCCEEDED'}}}
mock_client.return_value = event1
result = poll_status('id')
expected_result = event1
self.assertEqual(expected_result, result)
if __name__ == '__main__':
unittest.main()
Is you system configured with the aws cli. It seems like you might not have configured aws cli with appropriate access key id and secret key.
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')