Why do I need to call my mock like a method? - python

I've read all I can find with regards to Python 3 and mocking. Unfortunately, I still can't figure out why I need to verify assertions on mock() instead of mock when all documentation I've read use the latter.
Here is what I test looks like -
from unittest.mock import Mock, patch
from unittest import TestCase, skip
from suds.client import Client, ServiceDefinition
from run import WebService
import unittest
#patch('run.Client')
def test(self, mock):
service = WebService()
weather_id = 1234
weather = service.get_weather(weather_id)
mock().service.GetWeather.assert_called_once_with(weather_id)
run in this case is where WebService resides and Client is the suds client.
Printing mock.mock_calls I see -
[
call('SERVICE_WSDL', proxy={'https': 'PROXY', 'http': 'PROXY'}),
call().factory.create('AuthHeader'),
call().set_options(soapheaders=<MagicMock name='Client().factory.create()' id='37553264'>),
call().service.GetWeather(1234, '', '')
]
Mind you, my test passes. I'm simply wondering what I'm missing so I can better understand mocking in Python.

First of all, let's rename the variable because it's actually a mocked instance of run.Client:
#patch('run.Client')
def test(self, mock_client):
# ...
mock_client().service.GetWeather.assert_called_once_with(weather_id)
You're creating an instance of Client within run and you're using that in your code. But you're not actually mocking that instance in this test, you're mocking the class (run.Client is being patched).
So we have a mocked class and we're calling that class to create an instance. That instance is what your code actually uses. This means what you really want is access to the return value of the constructor of the class:
mock_client.return_value.service.GetWeather.assert_called_once_with(weather_id)
This is the same as your code except without calling mock_client(). Your current test code does something similar: call the mocked class, see what instance you get back and perform assertions on that. Yet the mock library provides the return_value attribute to do that already.

Related

Django how to prevent patched method running when created in super class [duplicate]

so I'm writing unit tests and I'm having trouble with the setUp function. From what I've seen, it's supposed to just execute code before your function and thus I could put anything that's repetitive in there. However, this function doesn't seem to be applying the mocks I've created as patch decorators over the entire class. This is a small piece of what I want it to look like:
#patch('geomet_data_registry.layer.base.get_today_and_now', new=mocked_get_date) # noqa
#patch('geomet_data_registry.layer.base.load_plugin', new=mocked_load_plugin)
#patch('geomet_data_registry.layer.base.TILEINDEX_PROVIDER_DEF', new=mocked_tileindex) # noqa
#patch('geomet_data_registry.layer.base.STORE_PROVIDER_DEF', new=mocked_store)
class TestInitBase(unittest.TestCase):
def setUp(self):
""" Code that executes before every function. """
self.maxDiff = None
self.today_date = \
datetime.now(timezone.utc).isoformat().replace('+00:00', 'Z')
mocked_get_date.return_value = self.today_date
self.base_layer = BaseLayer({'name': 'model_gem_global'})
def test_Init(self):
expected_values = {'items': [],...
base_layer_attr = self.base_layer.__dict__
self.assertDictEqual(expected_values, base_layer_attr, msg=None)
Here I mock the date received so it doesn't mess with my tests, I mock load_plugin, which when used returns a class instance of a certain plugin, I mock TileIndex which is an ES TileIndex and I mock the store, which is a redis store. If I use the code shown above, it doesn't work. When I instantiate the class BaseLayer inside setUp, none of my mocks are used and I get:
- 'receive_datetime': '2021-11-10T12:56:07.371067Z',
? ^^^
+ 'receive_datetime': '2021-11-10T12:56:07.371131Z',
? ^^^
- 'store': <MagicMock name='mock()' id='140158154534472'>,
- 'tileindex': <MagicMock name='mock()' id='140158154534472'>,
+ 'store': <BaseStore> Redis,
+ 'tileindex': <ElasticsearchTileIndex> http://localhost:9200,
However, before you tell me maybe my paths are wrong for the mocks or something like that, I can assure you that everything works fine, because the code works if I keep everything the same, except I repeat the class instantiation in every test function. Moreover, it will work if I keep this the same, but I name the setUp for example mySetUp and call it at the beginning of the function.
Everything works this way and I've already made all my tests without using the setUp at all because I remember thinking to myself "this thing is broken I'm not risking using this function in my tests".
Thanks!
By default the mock.patch decorator is applied to each function in the class starting with test. Since setUp does not start with test it will not be patched by the class decorator by default.
You can change this by setting patch.TEST_PREFIX (https://docs.python.org/3/library/unittest.mock.html#test-prefix). If you want it to apply to setUp as well you can do
patch.TEST_PREFIX = ("test", "setUp",)
and use the class decorators as before.
The problem is that the mock.patch decorator is applied to each test function, and during setUp the patching has not been done yet.
To use the same mocks for all tests, you have to start/stop mocking in setUp/tearDown instead. This could look something like:
class TestInitBase(unittest.TestCase):
def setUp(self):
self.data_patcher = patch('geomet_data_registry.layer.base.get_today_and_now',
new=mocked_get_date)
self.data_patcher.start()
self.plugin_patcher = patch('geomet_data_registry.layer.base.load_plugin',
new=mocked_load_plugin)
self.plugin_patcher.start()
...
self.today_date = \
datetime.now(timezone.utc).isoformat().replace('+00:00', 'Z')
mocked_get_date.return_value = self.today_date
self.base_layer = BaseLayer({'name': 'model_gem_global'})
def tearDown(self):
self.data_patcher.stop()
self.plugin_patcher.stop()
...
Admittedly this is not as nice as using the decorators. You can still use the decorators for the functions that don't have to already be patched in setUp, if there are any.
As a side note: with pytest this would be a bit less messy, as you can have the setup and teardown part in the same fixture.

How to patch an object instantiated outside of a function to reuse in every test python

I have a module where I instantiate an object outside of my functions, so I can reuse it:
from somewhere import client
client_obj = client()
def function_with_client1():
client_obj.foo1()
def function_with_client2()
client_obj.foo2()
I want to patch this client once so that in my tests I can reuse it across multiple tests, like so:
from unittest.mock import patch
from above import client, function_with_client1, function_with_client2
mocked_client = patch('above.client') #this doesn't work
def test_function_with_client1():
function_with_client1()
def test_function_with_client2():
function_with_client2()
The above patch doesn't work, it runs the tests with the real object instance. How can I correctly patch this in my test file once, so that the tests reuse it across all tests? I know I can decorate each test with a patch, however I want to reuse the same patched object like I do in my real module
Patching global objects is always a problem as they are initialized before patching. This can be worked around by reloading the module after patching, but the better solution is not to do the inialization globally.
You can use some kind of lazy initialization, a simple implementation would be something like this:
from somewhere import client
client_obj = None
def get_client():
global client_obj
if not client_obj:
client_obj = client()
return client_obj
def function_with_client1():
get_client().foo1()
...
Now client() is not called on importing the module, and you can mock it in your test.

setUp function for python unittest doesn't use mocks declared over the class

so I'm writing unit tests and I'm having trouble with the setUp function. From what I've seen, it's supposed to just execute code before your function and thus I could put anything that's repetitive in there. However, this function doesn't seem to be applying the mocks I've created as patch decorators over the entire class. This is a small piece of what I want it to look like:
#patch('geomet_data_registry.layer.base.get_today_and_now', new=mocked_get_date) # noqa
#patch('geomet_data_registry.layer.base.load_plugin', new=mocked_load_plugin)
#patch('geomet_data_registry.layer.base.TILEINDEX_PROVIDER_DEF', new=mocked_tileindex) # noqa
#patch('geomet_data_registry.layer.base.STORE_PROVIDER_DEF', new=mocked_store)
class TestInitBase(unittest.TestCase):
def setUp(self):
""" Code that executes before every function. """
self.maxDiff = None
self.today_date = \
datetime.now(timezone.utc).isoformat().replace('+00:00', 'Z')
mocked_get_date.return_value = self.today_date
self.base_layer = BaseLayer({'name': 'model_gem_global'})
def test_Init(self):
expected_values = {'items': [],...
base_layer_attr = self.base_layer.__dict__
self.assertDictEqual(expected_values, base_layer_attr, msg=None)
Here I mock the date received so it doesn't mess with my tests, I mock load_plugin, which when used returns a class instance of a certain plugin, I mock TileIndex which is an ES TileIndex and I mock the store, which is a redis store. If I use the code shown above, it doesn't work. When I instantiate the class BaseLayer inside setUp, none of my mocks are used and I get:
- 'receive_datetime': '2021-11-10T12:56:07.371067Z',
? ^^^
+ 'receive_datetime': '2021-11-10T12:56:07.371131Z',
? ^^^
- 'store': <MagicMock name='mock()' id='140158154534472'>,
- 'tileindex': <MagicMock name='mock()' id='140158154534472'>,
+ 'store': <BaseStore> Redis,
+ 'tileindex': <ElasticsearchTileIndex> http://localhost:9200,
However, before you tell me maybe my paths are wrong for the mocks or something like that, I can assure you that everything works fine, because the code works if I keep everything the same, except I repeat the class instantiation in every test function. Moreover, it will work if I keep this the same, but I name the setUp for example mySetUp and call it at the beginning of the function.
Everything works this way and I've already made all my tests without using the setUp at all because I remember thinking to myself "this thing is broken I'm not risking using this function in my tests".
Thanks!
By default the mock.patch decorator is applied to each function in the class starting with test. Since setUp does not start with test it will not be patched by the class decorator by default.
You can change this by setting patch.TEST_PREFIX (https://docs.python.org/3/library/unittest.mock.html#test-prefix). If you want it to apply to setUp as well you can do
patch.TEST_PREFIX = ("test", "setUp",)
and use the class decorators as before.
The problem is that the mock.patch decorator is applied to each test function, and during setUp the patching has not been done yet.
To use the same mocks for all tests, you have to start/stop mocking in setUp/tearDown instead. This could look something like:
class TestInitBase(unittest.TestCase):
def setUp(self):
self.data_patcher = patch('geomet_data_registry.layer.base.get_today_and_now',
new=mocked_get_date)
self.data_patcher.start()
self.plugin_patcher = patch('geomet_data_registry.layer.base.load_plugin',
new=mocked_load_plugin)
self.plugin_patcher.start()
...
self.today_date = \
datetime.now(timezone.utc).isoformat().replace('+00:00', 'Z')
mocked_get_date.return_value = self.today_date
self.base_layer = BaseLayer({'name': 'model_gem_global'})
def tearDown(self):
self.data_patcher.stop()
self.plugin_patcher.stop()
...
Admittedly this is not as nice as using the decorators. You can still use the decorators for the functions that don't have to already be patched in setUp, if there are any.
As a side note: with pytest this would be a bit less messy, as you can have the setup and teardown part in the same fixture.

Pytest Django function mocking APITestCase

I am trying to create tests involving sending GET requests to my API using pytest-django and I need a function used in the views to be mocked.
I have tried mocker from pytest-mock and unittest.mock.patch and every time I mock this function in some test case it remains mocked in the other tests as well.
First .py test file:
from unittest.mock import patch
from rest_framework.test import APITestCase
import pytest
#pytest.mark.django_db
class TestFirst(APITestCase):
#classmethod
def setUpClass(cls):
cls.patcher = patch(app.views.function)
cls.patcher.start()
#classmethod
def tearDownClass(cls):
cls.patcher.stop()
def test_something(self):
get_data = self.client.get('/some/url')
self.assertEqual(200, get_data.status_code)
and then followed by a test in some completely different .py file:
from rest_framework.test import APITestCase
import pytest
#pytest.mark.django_db
class TestSecond(APITestCase):
def test_something_else(self):
get_data = self.client.get('/some/url')
self.assertEqual(200, get_data.status_code)
When debugging the first test case, the method is patched correctly. However when running the second test, the method remains patched and the mock object keeps the number of calls received.
Am I missing something important?
EDIT: I tried both patching the file where the method is defined and name of the method in views, but always keep getting same result.
EDIT2: Worth noting that when I change the order of the tests, the second one completes correctly, but the first one is unable to have the method patched and calls it unpatched, therefore fails.
I resolved the issue by using the SimpleTestCase superclass. I have still no idea why this was happenning, but doesn't seem to be anymore.

How do I mock a component in my django application that reaches to an external service?

I have a method that reaches out to an external API and pulls in some content, then it performs some logic and proceeds. The problem is, while testing, I don't want my test cases to trigger this external API but I do want it to mock the response. Example
def create_animals(candidate):
if ExternalService.get_candidate_validity(candidate):
print('Things are good, go ahead')
#creates the animal objects etc....
But ExternalService.get_candidate_validity reaches out to an API that I want to mock. I know I can mock instances if it's like this:
get_candidate_validity_value = {'response': True}
c = ExternalService('someparamsthatineed')
c.get_candidate_validity = MagicMock(return_value=get_candidate_validity_value)
But how do I handle cases where the class is instantiated in a method that I end up calling to test?
If you had a python module animals.py that had this:
def create_animals(candidate):
if ExternalService.get_candidate_validity(candidate):
print('Things are good, go ahead')
#creates the animal objects etc....
You would mock it out this way in test_animals.py
from mock import MagicMock # or import mock from stdlib unittest in python 3
def test_create_animals():
from path.to.animals import ExternalService, create_animals
ExternalService.get_candidate_validity = MagicMock(return_value=True)
animals = create_animals('foo')
ExternalService.get_candidate_validity.assert_called_with('foo')
It is a best practice in unit testing to mock out all external services somehow so you're testing the unit, ie the function being tested, and nothing else.
Another way to do this is to use the patch functionality in the standard unit testing library.
https://docs.python.org/3/library/unittest.mock.html#attaching-mocks-as-attributes
>>> with patch('animals.ExternalService') as MockClass:
... MockClass.get_candidate_validity.return_value = 'foo'

Categories

Resources