I'm trying to mock a function. When I try to mock the function core.use_cases.add_owner_to_place the mock doesn't work. It keeps printing "Ouch".
I've tried to test mocked_add_owner_to_place.called and it returns False.
Does anyone know why it keeps using the real function even if I mock it?
views.py:
from core.use_cases import add_owner_to_place
class CreatePlace(LoginRequiredMixin, FormView):
template_name = 'place/create_place.html'
form_class = PlaceForm
success_url = reverse_lazy('place_list')
def form_valid(self, form):
place = form.save()
add_owner_to_place(place, self.request.user)
return super(CreatePlace, self).form_valid(form)
tests.py:
from unittest.mock import patch, Mock
#patch('core.use_cases.add_owner_to_place')
#patch('core.forms.PlaceForm.is_valid')
#patch('core.forms.PlaceForm.save')
def test_save_should_be_called(self, mocked_save, mocked_is_valid, mocked_add_owner_to_place):
self.client.post(reverse('place_create'), data={})
self.assertTrue(mocked_save.called)
uses_cases.py:
def add_owner_to_place(place, user):
print('Ouch')
So, searching around and looking some codes on github, I found out that I need to mock from the view even if the function belongs to the use_cases module.
So my code now is:
tests.py
from unittest.mock import patch, Mock
#patch('core.views.add_owner_to_place')
#patch('core.forms.PlaceForm.is_valid')
#patch('core.forms.PlaceForm.save')
def test_save_should_be_called(self, mocked_save, mocked_is_valid, mocked_add_owner_to_place):
self.client.post(reverse('place_create'), data={})
self.assertTrue(mocked_save.called)
I know that this works, but now I need to search why it works. I'll explain it when I figure it out.
Related
Here is a little class (in myapp/getters.py):
from django.contrib.auth.models import User
class UserGetter:
def get_user(self):
return User.objects.get(username='username')
I would like to mock out the call to User.objects.get, return a MagicMock, and test that the method returns what I injected. In myapp/tests/tests_getters.py:
from unittest import TestCase
from django.contrib.auth.models import User, UserManager
from mock import patch, create_autospec
from myapp.getters import UserGetter
class MockTestCase(TestCase):
#patch('myapp.getters.User', autospec=True)
def test(self, user_class):
user = create_autospec(User)
objects = create_autospec(UserManager)
objects.get.return_value = user
user_class.objects.return_value = objects
self.assertEquals(user, UserGetter().get_user())
But when I run this test (with python manage.py test myapp.tests.tests_getters) I get
AssertionError:
<MagicMock name='User.objects().get()' spec='User' id='4354507472'> !=
<MagicMock name='User.objects.get()' id='4360679248'>
Why do I not get back the mock I injected? How can I write this test correctly?
I think this is your problem:
user_class.objects.return_value = objects
You instruct the mock to have a function "objects" that returns the objects on the right side.
But your code never calls any objects() function. It accesses the User.objects property, User is a Mock here, so User returns a new Mock on property access.
I'm using mocking to test views.
tests.py
#patch('orders.views.OrderView.generate_merchant_uid')
def test_expected_price_is_registered_on_GET_request(self, mock_generate_merchant_uid):
self.client.get(reverse('orders:order'))
views.py
class OrderView(LoginRequiredMixin, View):
def generate_merchant_uid(self):
merchant_uid = "blah_blah_blah"
return merchant_uid
def get(self, request, *args, **kwargs):
merchant_uid = self.generate_merchant_uid()
request.session['merchant_uid'] = merchant_uid
return HttpResponse('a')
It occurs errors:
TypeError: <MagicMock name='generate_merchant_uid()' id='4431843456'> is not JSON serializable
It occurs error because I mocked generate_merchant_uid and it returns MagicMock and View trying to store this MagicMock in the request.session.
I think what I have to do is to mock request.session.
But have no idea how I can do that.
Need advices. Thanks.
The problem is not about mocking the session itself. You forgot to set what your mocked function should return. By default it returns a Mock object and it is trying to store it request session and converting it to JSON, there is where you got the error, Mock instance is not JSON serializable.
#patch('orders.views.OrderView.generate_merchant_uid')
def test_expected_price_is_registered_on_GET_request(self, mock_generate_merchant_uid):
mock_generate_merchant_uid.return_value = //here goes your mocked value
self.client.get(reverse('orders:order'))
For example:
mock_generate_merchant_uid.return_value = "blah_blah_blah"
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')
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 tried to mock form with mock.patch and can`t. I have this code
forms.py
class CreatePostForm(object):
pass
views.py:
from forms import CreatePostForm
def doit():
print CreatePostForm()
and I want to to test this view in isolation. I tried to patch form with mock.patch and i wrote something like that:
tests.py:
from mock import patch
import views
with patch('forms.CreatePostForm') as Form:
views.doit()
I tried to google for solution and find nothing
Answered: thanks #dstanek for good answer and good sample of code
When you use patch you specify the target of the object you want to mock. This is usually the place where it is imported, not where it is defined.
This is because by the time your test runs the views module has already been imported. If you are importing the class like I'm doing in my example below then the views module will contain a reference to the forms.CreatePostForm. So changing forms.CreatePostForm would not change this reference. Things would be different if you imported the module as specified forms.CreatePostForm in your view.
I've included an extremely minimal example below.
forms.py
class CreatePostForm(object):
pass
views.py:
from forms import CreatePostForm
def doit():
print CreatePostForm()
tests.py:
from mock import patch
import views
with patch('views.CreatePostForm') as Form:
views.doit()