Pass parameter to side_effect function for patching in unittest.mock - python

I'm using patch from unittest.mock to change the behavior of a remote API call in my test.
I have three different functions that return three different json files that represent the mock data to be returned from the API. For each mock api call, I am setting side_effect to be one of these functions. This pattern isn't DRY, but I don't know how to pass a parameter to a side_effect function.
The three mock api call functions look like this:
def mock_api_call_1():
with open('path/to/mock_1.json') as f:
mock_data = json.load(f)
return mock_data
Here's my test
class MyTest(TestCase):
def test_api(self):
with patch('mymodule.utils.api_call', side_effect=mock_api_call_1):
do_crud_operations()
self.assertEqual(MyModel.objects.all().count(), 10)
with patch('mymodule.utils.api_call', side_effect=mock_api_call_2):
do_crud_operations()
self.assertEqual(MyModel.objects.all().count(), 11)
How can I refactor this code to be able to pass a parameter to the side_effect (mock_call(1) instead of mock_call_1).
From the unittest docs, I see that:
side_effect: A function to be called whenever the Mock is called. See
the side_effect attribute. Useful for raising exceptions or
dynamically changing return values. The function is called with the
same arguments as the mock, and unless it returns DEFAULT, the return
value of this function is used as the return value.
I see that the function passed to side_effect takes the same arguments as the mock, but I'm still not sure how best to use mock to accomplish this. I'll eventually want to add more test cases, so I don't want to be hard-coding different mock_api_call functions.

Use a lambda function:
from unittest import TestCase, main
from unittest.mock import Mock, patch
import sys
def mock_api_call(x):
print(x)
class MyTest(TestCase):
def test_api(self):
with patch('sys.exit',
side_effect=lambda x: mock_api_call(x)) as m:
m(0)
sys.exit(0)
m(1)
sys.exit(1)
if __name__ == '__main__':
main()

I think the easiest way to do this is to set side_effect to a function that returns a function.
def mock_call(num):
def wrapper():
with open("path/to/mock_{num}.json") as f:
data = json.load(f)
return data
return wrapper
Now I can pass mock_call(1) to side_effect and it will behave as expected.

Related

Is it possible to check the call parameters in the test?

is it possible to check in the test with what parameters the method is called and what result it returns, if we call the main method run which calls the method I'm interested in - self.get_request().
file.py
class A:
def run():
some logic...
request = self.get_request()
some logic...
return response
test.py
from file.py import A
def test():
"""
Inside this test, I want to check the parameters and the value returned by the
get_request method, but I don't want to check it separately
I want to check it by calling the parent method - run
"""
instance = A()
response = instance.run()
assertions logic for instance.get_request..
I know that it is possible to mock a method and then we have access to the number of calls, parameters, etc. If what I'm asking is possible in some way through mock, I just want to add that my mock would have to have the same logic as the method it mocks (be the same).
What you are asking for is probably the wraps argument that can be used in patch - this allows you to mock a function, while it still retains the previous (or some other) functionality (note that the argument itself is described under Mock). As with any mock, this does allow you to test the calls and call args, but does not allow you to check the return value of the function. This has to be tested via its side effects (in your case via the returned response which should depend on the return value of get_request).
Here is an illustration for your case:
from unittest import mock
class A:
def run(self):
request = self.get_request(21)
return request
def get_request(self, foo):
return foo * 2
def test_run():
instance = A()
with mock.patch.object(instance, "get_request", wraps=instance.get_request) as mocked:
assert instance.run() == 42
mocked.assert_called_once_with(21)
In this case the mock calls the real get_request method and returns its result, while recording the call and the call args.
I added some argument to get_request for demonstration, and returned the result of the call directly in run - in your case this will differ of course, but the idea should be the same.

pytest - using patch as a decorator to patch a constant

I have an object MyObject which uses a constant defined in mymodule.constants.MYCONSTANT. I can successfully patch the constant using a context manger like so:
import pytest
from unittest.mock import patch
def test_constant(self):
with patch('mymodule.constants.MYCONSTANT', 3):
MyObject()
However, I can't figure out how to use the equivalent patching using patch as a decorator:
#patch('mymodule.constants.MYCONSTANT', 3)
def test_constant(self, mock_constant):
MyObject()
The above fails with a fixture mock_constant not found error. I tried using
#patch('mymodule.constants.MYCONSTANT', return_value=3)
But MYCONSTANT doesn't get replaced with the value of 3.
This is aligned to the behavior as documented:
unittest.mock.patch(target, new=DEFAULT, spec=None, create=False, spec_set=None, autospec=None, new_callable=None, **kwargs)
... If patch() is used as a decorator and new is omitted, the created mock is passed in as an extra argument to the decorated function.
So, if you want to pass the extra argument to the decorated function, don't set the new argument:
#patch('mymodule.constants.MYCONSTANT')
def test_constant(self, mock_constant):
...
Setting the new argument means no extra argument would be passed:
#patch('mymodule.constants.MYCONSTANT', 3)
def test_constant(self):
...
Guys the order of parameters matter so first mocks then fixtures, otherwise it will still throw you the same error.
#patch('server.app.UserValidator')
def test_first_mocks(self,mock_user_validator:MagicMock,client_fixture:FlaskClient):
# Arrange
input = {'password':'internal','username':'error'}
mock_user_validator.side_effect = ValueError('testing')
# Act
response = client_fixture.post('/api/users',json=input)
# Assert
mock_user_validator.assert_called_once()
assert response.status_code == 500

Pytest mock an object returned via yield and check whether a method of that object is called

I have a piece of code need to be tested via pytest
def my_function(value):
with some_generator() as gen:
gen.some_method(value)
I need to check whether the some_method has been called or not. I have used pytest-mock to mock the some_generator method to return a MagicMock and used that object to check if the method is called. But it is returning false. Also the return value I assigned is not reflected to the some_method
def test_myfunction(mocker):
generator = mocker.patch('some_generator')
mocked_obj = mock.MagicMock()
generator.return_value = mocked_obj
my_function(1)
assert mocked_obj.some_method.called
The test is always failing even though the gen.some_method(value) is called.
I think you have two problems:
You're not telling mocker where some_generator is. I think you need to include a module name.
You're mocking the return value of the generator, but not the __enter__() call that the with statement makes. You don't need a separate mock object for that, mocker already creates a mock object for return_value and any nested attributes.
Here's a fixed version of your test:
from scratch import my_function
def test_myfunction(mocker):
generator = mocker.patch('scratch.some_generator')
my_function(1)
assert generator.return_value.__enter__.return_value.some_method.called
Just for completeness, here's the runnable version of my_function that I used:
# scratch.py
def some_generator():
pass
def my_function(value):
with some_generator() as gen:
gen.some_method(value)

Mock a class and a class method in python unit tests

I'm using python's unittest.mock to do some testing in a Django app. I want to check that a class is called, and that a method on its instance is also called.
For example, given this simplified example code:
# In project/app.py
def do_something():
obj = MyClass(name='bob')
return obj.my_method(num=10)
And this test to check what's happening:
# In tests/test_stuff.py
#patch('project.app.MyClass')
def test_it(self, my_class):
do_something()
my_class.assert_called_once_with(name='bob')
my_class.my_method.assert_called_once_with(num=10)
The test successfully says that my_class is called, but says my_class.my_method isn't called. I know I'm missing something - mocking a method on the mocked class? - but I'm not sure what or how to make it work.
Your second mock assertion needs to test that you are calling my_method on the instance, not on the class itself.
Call the mock object like this,
my_class().my_method.assert_called_once_with(num=10)
^^
A small refactoring suggestion for your unittests to help with other instance methods you might come across in your tests. Instead of mocking your class in each method, you can set this all up in the setUp method. That way, with the class mocked out and creating a mock object from that class, you can now simply use that object as many times as you want, testing all the methods in your class.
To help illustrate this, I put together the following example. Comments in-line:
class MyTest(unittest.TestCase):
def setUp(self):
# patch the class
self.patcher = patch('your_module.MyClass')
self.my_class = self.patcher.start()
# create your mock object
self.mock_stuff_obj = Mock()
# When your real class is called, return value will be the mock_obj
self.my_class.return_value = self.mock_stuff_obj
def test_it(self):
do_something()
# assert your stuff here
self.my_class.assert_called_once_with(name='bob')
self.mock_stuff_obj.my_method.assert_called_once_with(num=10)
# stop the patcher in the tearDown
def tearDown(self):
self.patcher.stop()
To provide some insight on how this is put together, inside the setUp method we will provide functionality to apply the patch across multiple methods as explained in the docs here.
The patching is done in these two lines:
# patch the class
self.patcher = patch('your_module.MyClass')
self.my_class = self.patcher.start()
Finally, the mock object is created here:
# create your mock object
self.mock_stuff_obj = Mock()
self.my_class.return_value = self.mock_stuff_obj
Now, all your test methods can simply use self.my_class and self.mock_stuff_obj in all your calls.
This line
my_class.my_method.assert_called_once_with(num=10)
will work if my_method is a class method.
Is it the case?
Otherwise, if my_method is just an normal instance method, then you will need to refactor the function do_something to get hold of the instance variable obj
e.g.
def do_something():
obj = MyClass(name='bob')
return obj, obj.my_method(num=10)
# In tests/test_stuff.py
#patch('project.app.MyClass')
def test_it(self, my_class):
obj, _ = do_something()
my_class.assert_called_once_with(name='bob')
obj.my_method.assert_called_once_with(num=10)

Patch - Patching the class introduces an extra parameter?

First time using patch. I've tried to patch one of my classes for testing. Without the patch attempting to run gets past the test function definition, but with the patch the test function definition apparently requires another parameter and I get a
TypeError: testAddChannelWithNamePutsChannel() takes exactly 1 argument (2 given)
Error. The test code is the following:
import unittest
import mock
from notification.models import Channel, addChannelWithName, deleteChannelWithName
class TestChannel(unittest.TestCase):
#mock.patch('notification.models.Channel')
def testAddChannelWithNamePutsChannel(self):
addChannelWithName('channel1')
Channel.put.assert_called_with()
Why does it require an extra parameter with the patch and what should this parameter be? Thank you much!
Patch passes in an instance of the patched object to your test method (or to every test method if you are patching at the class level). This is handy because it lets you set return values and side effects, or check the calls made
from unittest.mock import patch
#patch('some_module.sys.stdout')
def test_something_with_a_patch(self, mock_sys_stdout):
mock_sys_stdout.return_value = 'My return value from stdout'
my_function_under_test()
self.assertTrue(mock_sys_stdout.called)
self.assertEqual(output, mock_sys_stdout.return_value)
If you just want to literally patch something out to ignore it then you can call patch with the following invocation
from unittest.mock import patch, Mock
#patch('some_module.sys.stdout', Mock())
def test_something_with_a_patch(self):
That replaces sys.stdout in the some_module with a Mock object and does not pass it to the method.
patch passes the patched object to the test function. Its documented here:
patch as function decorator, creating the mock for you and passing it
into the decorated function:
>>>
>>> #patch('__main__.SomeClass')
... def function(normal_argument, mock_class):
... print(mock_class is SomeClass)
...
>>> function(None)
True

Categories

Resources