Trying to understand mocking/patching and I have a restful API project with three files (FYI, I'm using flask)
class1.py
domain.py
test_domain.py
class1.py file content:
class one:
def addition(self):
return 4+5
domain.py file content:
from class1 import one
class DomainClass(Resource):
def post(self):
test1 = one()
val = test1.addition()
return {'test' : val }
test_domain.py file content:
import my_app
from flask_api import status
from mock import patch
app = my_app.app.test_client()
def test_post():
with patch('domain.one') as mock:
instance = mock.return_value
instance.addition.return_value = 'yello'
url = '/domain'
response = app.post(url)
print response.data
assert status.HTTP_200_OK == response.status_code
assert mock.called
For my test_domain.py file, I've also tried this...
#patch('domain.one')
def test_post(mock_domain):
mock_domain.addition.return_value = 1
url = '/domain'
response = app.post(url)
print response.data
assert status.HTTP_200_OK == response.status_code
My assert for the status of 200 passes, however, the problem is that I'm not able to mock or patch the addition method to give me value of 1 in place of 9 (4+5). I also tried doing 'assert mock.called' and it failes as well. I know I should be mocking/patching where the 'one()' method is used, i.e. in domain.py not in class1.py. But I tried even mocking class1.one in place of domain.one and I still kept getting 9 and not 1. What am I doing wrong ?
******** Update
I've another dilemma on the same issue, I tried doing this in the test_domain file instead of patching....
from common.class1 import one
def test_post():
one.addition = MagicMock(return_value=40)
url = '/domain'
response = app.post(url)
print response.data
assert status.HTTP_200_OK == response.status_code
Question
In update above, I did not do a mock at the place where it is used (i.e.: domain.one.addition = MagicMock(...) and it still worked !!!! It seems it may be doing a global change. Why did this work ?
In the above example, 'one' is a class in the module class1.py. If I change this class 'one' to a function in class1.py, mocking does not work. It seems this function 'one' residing in module class1.py can not be mocked like this...one.return_value = 'xyz', why? Can it be mocked globally ?
There are some issues in your code. In the first example you forgot that patch() is applied in with context and the original code is recovered when the context end. Follow code should work:
def test_post():
with patch('domain.one') as mock:
instance = mock.return_value
instance.addition.return_value = 'yello'
url = '/domain'
response = app.post(url)
print response.data
assert status.HTTP_200_OK == response.status_code
assert mock.called
assert response.data['test'] == 'yello'
The second one have an other issue: if you want patch just addition method you should use:
#patch('domain.one.addition')
def test_post(mock_addition):
mock_addition.return_value = 1
...
assert mock_addition.called
assert response.data['test'] == 1
If you want mock all one class you should set the return value of addition method of mock instance returned by mock_domain call like in your first example:
#patch('domain.one')
def test_post(mock_domain):
mock_addition = mock_domain.return_value.addition
mock_addition.return_value = 1
...
assert mock_addition.called
assert response.data['test'] == 1
Related
I'm trying to mock out the method requests.get() of the module requests and set the attribute side_effect of the obtained instance of the Mock class.
I would like to associate a different status_code for each side effect value but I didn't succeed so far.
def test_func1(mocker):
side_effect = ["Ok",'','','Failed']
# This line should be changed
fake_resp.status_code = 200
fake_resp = mocker.Mock()
fake_resp.json = mocker.Mock(side_effect=side_effect)
mocker.patch("app.main.requests.get", return_value=fake_resp)
# func1 executes multiple API calls using requests.get()
# and status_code is needed
a = func1(a, b)
assert a == "something"
I have not been able to find a way (in the doc and SO) to associate the status_code for each mock request.
I was thinking about something like this but it's obviously not working:
def test_func1(mocker):
side_effect = [(status_code=200, return="Ok"),
(status_code=204, return=""),
(status_code=204, return=""),
(status_code=500, return="Failed")]
....
EDIT: add the code of func1()
from datetime import datetime, timedelta
import requests
def func1(days, delta_1):
"""
days: number of days before first search (80, 25, 3)
delta_1: number of days for the date range search (40, 20, 15)
"""
now = datetime.now()
start_date = now + timedelta(days=days)
# Var to stop loop when price is found
loop_stop = 0
# Var to stop loop when search date is more than a year later
delta_time = 0
price = 0
departureDate = "n/a"
# For loop to check prices till one year.
while loop_stop == 0 and delta_time < (365 - days):
date_range = (
(start_date + timedelta(days=delta_time)).strftime("%Y%m%d")
+ "-"
+ (start_date + timedelta(days=delta_time + (delta_1 / 2))).strftime(
"%Y%m%d"
)
)
# Needs to be mocked
response = requests.get("fake_url_using_date_range_var")
if response.status_code == 204:
print("No data found on this data range")
delta_time += delta_1
elif response.status_code == 200:
price = response.json()["XXX"][0]
departureDate = response.json()["YYY"][0]
loop_stop = 1
else:
raise NameError(
response.status_code,
"Error occured while querying API",
response.json(),
)
return price, departureDate
Possible solution with module unittest (not pytest)
I have written this answer before #baguette added the code of its function func1(), so I have created a file called my_file_01.py which contains my production function func1():
import requests
def func1():
response1 = 'empty1'
response2 = 'empty2'
r = requests.get('http://www.someurl.com')
if r.status_code == 200:
response1 = r.json()
r = requests.get('http://www.some_other_url.com')
if r.status_code == 500:
response2 = r.json()
return [response1, response2]
As you can see func1() calls requests.get() two times and checks the status code of responses.
I have inserted the test code in a different file with the following content:
import unittest
from unittest import mock
from my_file_01 import func1
def request_resp1(url):
response_mock = mock.Mock()
response_mock.status_code = 200
response_mock.json.return_value = {'key1': 'value1'}
# the function return an instance of class Mock
return response_mock
def request_resp2(url):
response_mock = mock.Mock()
response_mock.status_code = 500
response_mock.json.return_value = "Failed"
# the function return an instance of class Mock
return response_mock
class TestFunc1(unittest.TestCase):
#mock.patch("my_file_01.requests")
def test_func1(self, mock_requests):
print("test func1()")
mock_requests.get.side_effect = [request_resp1(""), request_resp2("")]
[response1, response2] = func1()
print("response1 = " + str(response1))
print("response2 = " + str(response2))
if __name__ == "__main__":
unittest.main()
The test file defines the test class TestFunc1 which contains the test method test_func1().
Furthermore the file defines 2 functions called request_resp1() and request_resp2().
These functions are used to define different response values when it is called the method requests.get() by the code of func1(); to be more accurate:
the first call to requests.get() assigns to variable r the Mock object with status_code = 200 so it simulates a success;
the second call to requests.get() assigns to variable r the Mock object with status_code = 500 so it simulates a failure.
If you try to execute the test code you can see that func1() return different values for the 2 different status_codes of the response. The output of the execution is composed by the 3 print() instructions present in test_func1() and is the followed:
test func1()
response1 = {'key1': 'value1'}
response2 = Failed
Useful links
For detail about side_effect attribute see its documentation.
request is a Python module and this is a minimal documentation about it.
Based on the solution from #frankfalse, the two mocking functions can be replaced by a class.
class MockResponse:
def __init__(self, json_data, status_code=requests.codes.ok):
self.json_data = json_data
self.status_code = status_code
def json(self):
return self.json_data
With the previous class the file contained the test code of #frankfalse becomes:
import unittest
from unittest import mock
from my_file_01 import func1
import requests
class MockResponse:
def __init__(self, json_data, status_code=requests.codes.ok):
self.json_data = json_data
self.status_code = status_code
def json(self):
return self.json_data
class TestFunc1(unittest.TestCase):
#mock.patch("my_file_01.requests")
def test_func1(self, mock_requests):
print("test func1()")
mock_requests.get.side_effect = [MockResponse({'key1': 'value1'}), MockResponse('Failed', 500)]
[response1, response2] = func1()
print("response1 = " + str(response1))
print("response2 = " + str(response2))
The differences are:
the mocking functions request_resp1() and request_resp2() can be removed
it is necessary to add: import requests for the presence of the assignment status_code=requests.codes.ok in the __init__() method
the instruction for the definition of side_effect becomes:
mock_requests.get.side_effect = [MockResponse({'key1': 'value1'}), MockResponse('error', 500)]
Because ith has been created the class MockResponse, the class Mock of the module unittest.mock is not used.
I had created a simple example to illustrate my issue. First is the setup say mydummy.py:
class TstObj:
def __init__(self, name):
self.name = name
def search(self):
return self.name
MyData = {}
MyData["object1"] = TstObj("object1")
MyData["object2"] = TstObj("object2")
MyData["object3"] = TstObj("object3")
def getObject1Data():
return MyData["object1"].search()
def getObject2Data():
return MyData["object2"].search()
def getObject3Data():
return MyData["object3"].search()
def getExample():
res = f"{getObject1Data()}{getObject2Data()}{getObject3Data()}"
return res
Here is the test that failed.
def test_get_dummy1():
dummy.MyData = MagicMock()
mydummy.MyData["object1"].search.side_effect = ["obj1"]
mydummy.MyData["object2"].search.side_effect = ["obj2"]
mydummy.MyData["object3"].search.side_effect = ["obj3"]
assert mydummy.getExample() == "obj1obj2obj3"
The above failed with run time error:
/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/unittest/mock.py:1078: StopIteration
Here is the test that passed:
def test_get_dummy2():
dummy.MyData = MagicMock()
mydummy.MyData["object1"].search.side_effect = ["obj1", "obj2", "obj3"]
assert mydummy.getExample() == "obj1obj2obj3"
Am I missing something? I would have expected test_get_dummy1() to work and test_get_dummy2() to fail and not vice versa. Where and how can I find/learn more information about mocking to explain what is going on...
MyData["object1"] is converted to this function call: MyData.__getitem__("object1"). When you call your getExample method, the __getitem__ method is called 3 times with 3 parameters ("object1", "object2", "object3").
To mock the behavior you could have written your test like so:
def test_get_dummy_alternative():
mydummy.MyData = MagicMock()
mydummy.MyData.__getitem__.return_value.search.side_effect = ["obj1", "obj2", "obj3"]
assert mydummy.getExample() == "obj1obj2obj3"
Note the small change from your version: mydummy.MyData["object1"]... became: mydummy.MyData.__getitem__.return_value.... This is the regular MagicMock syntax - we want to to change the return value of the __getitem__ method.
BONUS:
I often struggle with mock syntax and understanding what's happening under the hood. This is why I wrote a helper library: the pytest-mock-generator. It can show you the actual calls made to the mock object.
To use it in your case you could have added this "exploration test":
def test_get_dummy_explore(mg):
mydummy.MyData = MagicMock()
mydummy.getExample()
mg.generate_asserts(mydummy.MyData, name='mydummy.MyData')
When you execute this test, the following output is printed to the console, which contains all the asserts to the actual calls to the mock:
from mock import call
mydummy.MyData.__getitem__.assert_has_calls(calls=[call('object1'),call('object2'),call('object3'),])
mydummy.MyData.__getitem__.return_value.search.assert_has_calls(calls=[call(),call(),call(),])
mydummy.MyData.__getitem__.return_value.search.return_value.__str__.assert_has_calls(calls=[call(),call(),call(),])
You can easily derive from here what has to be mocked.
I Have this function that I wish to test, this is how it looks like.
def myfunction():
response = requests.post(url,params=params,headers=headers,data=data)
response = response.json()
return response["Findme"].lower()
My test script:
#mock.patch('requests.post',return_value="{'Findme': 'test'}")
def test_myfunction(mocked_post):
**assert myfunction() == "test"**
When i run the test, i keep getting None for myfunction(), but when i remove the response.json() it works?
Please can anyone assist me.
As mentioned by Deep Space, your returned object does not have a json method, as it is of type str. If you want to have the same behavior as in the tested function, you have to provide an object with that method:
class MockResponse:
"""Mocks relevant part of requests.Response"""
def __init__(self, s):
self.json_string = s
def json(self):
return json.loads(self.json_string)
#mock.patch("requests.post", return_value=MockResponse('{"Findme": "test"}'))
def test_myfunction(mocked_post):
assert myfunction() == "test"
This way, an object of type MockResponse is returned from the mocked post function, that can be deserialized using json().
You could also mock the return value of json directly instead of mocking the return value of post, if you want to do this. In this case you wouldn't need an extra class:
#mock.patch("requests.post")
def test_myfunction(mocked_post):
mocked_post.return_value.json.return_value = {"Findme": "test"}
assert myfunction() == "test"
My goal is to write unit tests for a REST API that interfaces with Flash Memory on a device. To do that, I need a way to mock the class that interfaces with Flash Memory.
I attempted to do that by using a Python Dataclass in the Mock, but I've discovered I do not have any way to set initial values prior to each test. As a result, each test case is getting values that are set by the previous test case. I need to fix that.
To test the API, I'm using the following code:
#dataclass
class FlashMemoryMock:
mac_address: str = 'ff:ff:ff:ff:ff:ff'
#pytest.fixture
def client(mocker):
mocker.patch('manufacturing_api.bsp.flash_memory.FlashMemory', new=FlashMemoryMock)
app = connexion.App(__name__, specification_dir='../openapi_server/openapi/')
app.app.json_encoder = JSONEncoder
app.add_api('openapi.yaml', pythonic_params=True)
app.app.config['TESTING'] = True
with app.app.test_client() as client:
yield client
def test_get_mac_address(client):
"""Test case for get_mac_address
Get the MAC Address
"""
headers = {
'Accept': 'application/json',
}
response = client.open(
'/mac_address',
method='GET',
headers=headers)
assert response.status_code == 200
assert response.is_json
assert response.json.get('status') == 'success'
assert response.json.get('mac_address') == 'ff:ff:ff:ff:ff:ff'
This test case will pass because the FlashMemoryMock Dataclass initializes mac_address to ff:ff:ff:ff:ff:ff Unfortunately it would fail if I run it after a test_put_mac_address test case if that test changes the mac_address value.
The flash memory controller code looks like this:
flash_memory = FlashMemoryWrapper()
def get_mac_address(): # noqa: E501
return flash_memory.get_mac_address()
The FlashMemoryWrapper class validates inputs (i.e. is the user trying to set a valid Mac Address) and includes the following code:
class FlashMemoryWrapper:
def __init__(self):
# Initialize the Flash controller
self.flash_memory = FlashMemory()
It's this FlashMemory class that I am trying to replace with a Mock. When I debug the test cases, I have verified FlashMemoryWrapper.flash_memory is referencing FlashMemoryMock. Unfortunately I no longer have any way to set initial values in the FlashMemoryMock Dataclass.
Is there a way to set initial values? Or should I set up the Mock a different way?
I think what you are looking for can be achieved with a bit of meta-programming in tandem with parametrization of fixtures.
def initializer(arg):
#dataclass
class FlashMemoryMock:
mac_address: str = arg
return FlashMemoryMock
#pytest.fixture
def client(mocker, request):
mocker.patch('manufacturing_api.bsp.flash_memory.FlashMemory', new=initializer(request.param))
app = connexion.App(__name__, specification_dir='../openapi_server/openapi/')
app.app.json_encoder = JSONEncoder
app.add_api('openapi.yaml', pythonic_params=True)
app.app.config['TESTING'] = True
with app.app.test_client() as client:
yield client
#pytest.mark.parametrize('client', ['ff:ff:ff:ff:ff:ff'], indirect=['client'])
def test_get_mac_address(client):
"""Test case for get_mac_address
Get the MAC Address
"""
headers = {
'Accept': 'application/json',
}
response = client.open(
'/mac_address',
method='GET',
headers=headers)
assert response.status_code == 200
assert response.is_json
assert response.json.get('status') == 'success'
assert response.json.get('mac_address') == 'ff:ff:ff:ff:ff:ff'
# some other test with a different value for mac address
#pytest.mark.parametrize('client', ['ab:cc:dd'], indirect=['client'])
def test_put_mac_address(client):
# some code here
thanks for your time.
i'd like to know when testing views is it better to create a request object using RequestFactory().get() or Client().get() or calling the view directly
RequestFactory():
from django.test import RequestFactory
from django.urls import reverse
def test_profile_view(self):
p1 = mixer.blend(Profile)
path = reverse('profile', kwargs={'pk': p1.id})
request = RequestFactory().get(path)
request.user = p1.user
response2 = views.profile_view(request, pk=p1.id)
assert response.status_code == 200
assert response.streaming == False
assert response2.charset == 'utf-8'
assert response2.status_code == 200
Client():
from django.test import Client
class TestView():
def test_profile_view(self):
p1 = mixer.blend(Profile)
path = reverse('profile', kwargs={'pk': p1.id})
response= Client().get(path)
response.user = p1.user
response2 = views.profile_view(request, pk=p1.id)
assert response.status_code == 200
assert response.streaming == False
assert response2.charset == 'utf-8'
assert response2.status_code == 200
and would like to understand the difference please
If you use the RequestFactory and call the view, then you avoid the middlewares. This has the benefit that less codes gets executed and your test is faster.
But it has the drawback, that things might be a bit different on the production site, since on prod the middlewares get called.
I use Django since several years, and I am unsure again and again what is better. It makes no big difference.
BTW: This looks strange: response.user = p1.user