mocking a function within a class method - python

I want to mock a function which is called within a class method while testing the class method in a Django project. Consider the following structure:
app/utils.py
def func():
...
return resp # outcome is a HTTPResponse object
app/models.py
from app.utils import func
class MyModel(models.Model):
# fields
def call_func(self):
...
func()
...
app/tests/test_my_model.py
from django.test import TestCase
import mock
from app.models import MyModel
class MyModelTestCase(TestCase):
fixtures = ['my_model_fixtures.json']
def setUp(self):
my_model = MyModel.objects.get(id=1)
#mock.patch('app.utils.func')
def fake_func(self):
return mock.MagicMock(headers={'content-type': 'text/html'},
status_code=2000,
content="Fake 200 Response"))
def test_my_model(self):
my_model.call_func()
... # and asserting the parameters returned by func
When I run the test the mock function fake_func() is avoided and the real func() is called instead. I guess the scope in the mock.patch decorator might be wrong, but I couldn't find a way to make it work. What should I do?

There are three problems with your code:
1) As Daniel Roseman mentioned, you need to patch the module where the function is called, not where it is defined.
2) In addition, you need to decorate the test method that will actually be executing the code that calls the mocked function.
3) Finally, you also need to pass the mocked version in as a parameter to your test method, probably something like this:
fake_response = mock.MagicMock(headers={'content-type': 'text/html'},
status_code=2000,
content="Fake 200 Response"))
class MyModelTestCase(TestCase):
fixtures = ['my_model_fixtures.json']
def setUp(self):
my_model = MyModel.objects.get(id=1)
#mock.patch('app.models.func', return_value=fake_response)
def test_my_model(self, fake_response): # the mock goes in as a param or else you get number of arguments error!
my_model.call_func()
self.assertTrue(fake_response.called)

As the docs explain, you need to mock func in the place it is called, not where it is defined. So:
#mock.patch('app.models.func')

Related

Check the instance of a Mocked method called once

My function looks for a Actor object in the database and calls its do_something() method with a passed argument.
from my_app.models import Actor
def my_function(id, stuff):
actor = Actor.objects.get(id=id)
return actor.do_something(stuff)
I want my unit test to check two things :
1. my_function finds the Actor I want.
2. my_function calls the actor's do_something method as expected.
from unittest import mock
from django.test import TestCase
from my_app.models import Actor
from my_app.views import my_function
class ViewsTestCase(TestCase):
#classmethod
def setUpTestData(cls):
self.actor = Actor.objects.create(id=42, name='John')
def test_my_function(self):
with mock.patch.object(Actor, 'do_something') as mock_do:
my_function(id=42, stuff='a-short-string')
mock_do.assert_called_once_with('a-short-string')
This works to make sure my_function called do_something like I wanted but I don't know how to be sure it found the Actor I asked him to find. This test would pass even if my_function found the wrong actor. Is there any way to check that ?
First of all, I am not sure if this is the best practice of asserting on self param of a mocked method.
By adding autospec=True to your mock statement, the self param itself will become accessible in mocked object call_args. To be more clear your test case would be something like this:
from unittest import mock
from django.test import TestCase
from my_app.models import Actor
from my_app.views import my_function
class ViewsTestCase(TestCase):
#classmethod
def setUpTestData(cls):
self.actor = Actor.objects.create(id=42, name='John')
def test_my_function(self):
with mock.patch.object(Actor, 'do_something', autospec=True) as mock_do:
^^^^^^^^^^^^^
my_function(id=42, stuff='a-short-string')
mock_do.assert_called_once_with(self.actor, 'a-short-string')

pytest monkeypatch.setattr() inside of test class method

I have a test class with few test methods and I want to patch some app classes and methods from the test methods.
In pytest docs I found an example of how to use monkeypatch module for tests. It that example all tests are just functions, not testclass methods.
But I have a class with test methods:
class MyTest(TestCase):
def setUp():
pass
def test_classmethod(self, monkeypatch):
# here I want to use monkeypatch.setattr()
pass
And just passing monkeypatch as method param is obviously doesn't work. So looks like py.test magic doesn't work this way.
So the question is simple and maybe stupid: how can I use monkeypatch.setattr() for pytest inside from the test class method?
It can't work in this form
While pytest supports receiving fixtures via test function arguments
for non-unittest test methods, unittest.TestCase methods cannot
directly receive fixture function arguments as implementing that is
likely to inflict on the ability to run general unittest.TestCase test
suites.
You might create monkeypatch directly
from _pytest.monkeypatch import MonkeyPatch
class MyTest(TestCase):
def setUp():
self.monkeypatch = MonkeyPatch()
def test_classmethod(self):
self.monkeypatch.setattr ...
...
or create own fixture, which will add monkeypatch to your class, and use #pytest.mark.usefixtures
#pytest.fixture(scope="class")
def monkeypatch_for_class(request):
request.cls.monkeypatch = MonkeyPatch()
#pytest.mark.usefixtures("monkeypatch_for_class")
class MyTest(TestCase):
def setUp():
pass
def test_classmethod(self):
self.monkeypatch.setattr ...
...
I had exactly the same problem.
This works perfectly
import unittest
import pandas as pd
from _pytest.monkeypatch import MonkeyPatch
from src.geipan_data import loadLongitudeLatitudeDateTestimony
class TestGeipanData(unittest.TestCase):
def setUp(self):
self.monkeypatch = MonkeyPatch()
def test_loadLongitudeLatitudeDateTestimony(self):
def read_csv(*args, **kwargs):
return pd.DataFrame({
'obs_date_heure': ['2010-05-21', '1926-05-21'],
'obs_1_lon': [45.123, 78.4564],
'obs_1_lat': [32.123, 98.4564],
})
self.monkeypatch.setattr(pd, 'read_csv', read_csv)
df = loadLongitudeLatitudeDateTestimony()
self.assertListEqual(
df.columns.values.tolist(),
['obs_date_heure', 'obs_1_lon', 'obs_1_lat']
)
In this example I do mock the pd.read_csv method with monkey patch and I uses asserListEqual that extends from unittest.TestCase

Unable to patch class instantiated by the tested class using unittest

I am trying to patch a class that is instantiated by the class I am trying to test, but it doesn't work. I have read the various docs but still haven't found what I am doing wrong. Here is the code snippet:
In tests/Test.py:
from module.ClassToTest import ClassToTest
class Test(object):
#mock.patch('module.ClassToPatch.ClassToPatch', autospec = False)
def setUp(self, my_class_mock):
self.instance = my_class_mock.return_value
self.instance.my_method.return_value = "def"
self.class_to_test = ClassToTest()
def test(self):
val = self.class_to_test.instance.my_method() #Returns 'abc' instead of 'def'
self.assertEqual(val, 'def')
In module/ClassToPatch.py:
class ClassToPatch(object):
def __init__(self):
pass
def my_method(self):
return "abc"
In module/ClassToTest.py:
from module.ClassToPatch import ClassToPatch
class ClassToTest(object):
def __init__:
# Still instantiates the concrete class instead of the mock
self.instance = ClassToPatch()
I know in this case I could easily inject the dependency, but this is just an example. Also, we use a single class per file policy, with the file named like the class, hence the weird import naming.
As norbert mentions, the fix is to change the mock line from
#mock.patch('module.ClassToPatch.ClassToPatch', autospec = False)
to
#mock.patch('module.ClassToTest.ClassToPatch', autospec = False)
According to the docs:
The patch() decorator / context manager makes it easy to mock classes or objects in a module under test. The object you specify will be replaced with a mock (or other object) during the test and restored when the test ends.
You are testing the ClassToTest module, not the ClassToPatch module.

Django UnitTest with Mock

I am writing an Unit-Test for a Django class-based view.
class ExampleView(ListView):
def get_context_data(self, **kwargs):
context = super(EampleView, self).get_context_data(**kwargs)
## do something else
def get_queryset(self, **kwargs):
return self.get_data()
def get_data(self):
call_external_API()
## do something else
The key issue is that call_external_API() in get_data().
When I am writing Unit-test, I don't really want to call external API to get data. First, that will cost my money; second, I can easily test that API in another test file.
I also can easily test this get_data() method by having an unit-test only for it and mock the output of call_external_API().
However, when I test this whole class-based view, I simply will do
self.client.get('/example/url/')
and check the status code and context data to verify it.
In this case, how do I mock this call_external_API() when I am testing the whole class-based view?
What your are looking for is patch from unittest.mock. You can patch call_external_api() by a MagicMock() object.
Maybe you want to patch call_external_api() for all your tests in class. patch give to you essentialy two way to do it
decorate the test class
use start() and stop() in setUp() and tearDown() respectively
Decorate a class by patch decorator is like decorate all test methods (see documentation for details) and the implementation will be very neat. Follow example assume that your view is in my_view module.
#patch("my_view.call_external_api", autospec=True)
class MyTest(unittest.TestCase):
def setUp(self):
self.client = Client()
def test_get_data(self, mock_call_external_api):
self.client.get('/example/url/')
self.assertTrue(mock_call_external_api.called)
More sophisticate examples can be build and you can check how you call mock_call_external_api and set return value or side effects for your API.
I don't give any example about start and stop way to do it (I don't really like it) but I would like to spend some time on two details:
I assumed that in your my_view module you define call_external_api or you import it by from my_API_module import call_external_api otherwise you should pay some attention on Where to patch
I used autospec=True: IMHO it should be used in every patch call and documentation explain why very well
You can mock the call_external_api() method when testing the classed based view with something like this:
import modulea
import unittest
from mock import Mock
class ExampleTestCase(unittest.TestCase):
def setUp(self):
self.call_external_api = modulea.call_external_api
def tearDown(self):
modulea.call_external_api = self.call_external_api
def get_data(self):
modulea.call_external_api = Mock(return_value="foobar")
modulea.call_external_api()
## do something else

I can't get mock to work

I'm trying to use a mock for unit testing with Python/Django but I just can't get it to work. The mock acts like it has never been called.
tests.py
from my_module import my_library
my_library = MagicMock()
class MyTest(TestCase):
def test_add(self):
acronym = 'TEST'
m = MyModel(acronym=acronym)
m.save()
my_library.add.assert_called_with(acronym=acronym)
my_library.py
def add(acronym):
# Processing...
models.py
class MyModel(Model):
acronym = CharField(max_length=4)
def save(self):
my_library.add(acronym=self.acronym)
super(MyModel, self).save(*args, **kwargs)
My library works, I know the function add is correctly called. But the mock doesn't seem to work because it just raises en exception when I run the unit tests:
AssertionError: Expected call: add(acronym='TEST')
Not called
I think I don't correctly use the mock thing. Can anyone help or give advice please?
That is because it is never called. The first line of your program does this:
my_library = __import__("my_module")
The next line just overshadows the first. It does not alter my_module in any way, thus all your other code just calls the original code instead of the mock.
Take a look at mock.patch instead. Either like this:
from mock import patch
import my_module as my_library
class MyTest(TestCase):
def test_add(self):
acronym = 'TEST'
with patch.object(my_library, 'add') as add_method:
m = MyModel(acronym=acronym)
m.save()
add_method.assert_called_with(acronym=acronym)
Or using the original module name:
from mock import patch
class MyTest(TestCase):
def test_add(self):
acronym = 'TEST'
with patch('my_module.add') as add_method:
m = MyModel(acronym=acronym)
m.save()
add_method.assert_called_with(acronym=acronym)
Try using patch:
from mock import patch
from my_module import my_library
class MyTest(TestCase):
def test_add(self):
acronym = 'TEST'
with patch('my_library.add') as add_method:
m = MyModel(acronym=acronym)
m.save()
add_method.assert_called_with(acronym=acronym)
Hope that works for you.

Categories