Django UnitTest with Mock - python

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

Related

inject library dependencies into django model

I have a Django model that makes use of some libraries which I would like to be able to override. For instance, when testing I'd like to pass a mock instead of having my model tightly coupled. I can do this in python, but for the life of me I can't figure out how to do it with a Django model. Here's a simplified example not using Django:
import requests
class APIClient:
def __init__(self, **kwargs):
self.http_lib = kwargs.get("http_lib", requests)
def get_url(self, url):
return self.http_lib.get(url)
For regular use of this class I can still use requests but if I want to use a different library for some reason or if I want to test certain outcomes, I can invoke the class with client = APIClient(http_lib=MockRequests())
But how do I do that with a Django model? If I try to pass kwargs that aren't backed by a database field Django throws an error. Overriding __init__ is not considered a good practice either. Is there a way in Django to set and get a value that isn't backed by a database column?
Do you have a settings.TEST var? If so, you could make http_lib a function that returns the proper lib:
from django.conf import settings
def get_http_lib(mock=None):
if not mock:
return requests
return MockRequests()
class APIClient(Model):
def __init__(self, **kwargs):
# ...whatever...
#property
def some_column(self):
http_lib = get_http_lib(settings.TEST)
# ...etc...
Not ideal, but passable.
PRE-EDIT ANSWER (doesn't work):
What if you setattr subsequent to instantiating the Model?
# In model...
class APIClient(Model):
def __init__(self, **kwargs):
self.http_lib = requests
# ...etc...
# In tests...
client = APIClient()
setattr(client, 'http_lib', MockRequests())

passing function into a class and using it as a decorator of class methods

first I created some user management functions I want to use everywhere, and bound them to cherrypy, thinking I could import cherrypy elsewhere and they would be there. Other functions seem to import fine this way, when not used as decorators.
from user import validuser
cherrypy.validuser = validuser
del validuser
that didn't work, so next I tried passing the function into the class that is a section of my cherrypy site (/analyze) from the top level class of pages:
class Root:
analyze = Analyze(cherrypy.validuser) #maps to /analyze
And in the Analyze class, I referred to them. This works for normal functions but not for decorators. why not?
class Analyze:
def __init__(self, validuser):
self.validuser = validuser
#cherrypy.expose
#self.validuser(['uid'])
def index(self, **kw):
return analysis_panel.pick_data_sets(user_id=kw['uid'])
I'm stuck. How can I pass functions in and use them as decorators. I'd rather not wrap my functions like this:
return self.validuser(analysis_panel.pick_data_sets(user_id=kw['uid']),['uid'])
thanks.
ADDED/EDITED: here's what the decorator is doing, because as a separate issue, I don't think it is properly adding user_id into the kwargs
def validuser(old_function, fetch=['uid']):
def new_function(*args, **kw):
"... do stuff. decide is USER is logged in. return USER id or -1 ..."
if USER != -1 and 'uid' in fetch:
kw['uid'] = user_data['fc_uid']
return old_function(*args, **kw)
return new_function
only the kwargs that were passed in appear in the kwargs for the new_function. Anything I try to add isn't there. (what I'm doing appears to work here How can I pass a variable in a decorator to function's argument in a decorated function?)
The proper way in CherryPy to handle a situation like this is to have a tool and to enable that tool on the parts of your site that require authentication. Consider first creating this user-auth tool:
#cherrypy.tools.register('before_handler')
def validate_user():
if USER == -1:
return
cherrypy.request.uid = user_data['fc_uid']
Note that the 'register' decorator was added in CherryPy 5.5.0.
Then, wherever you wish to validate the user, either decorate the handler with the tool:
class Analyze:
#cherrypy.expose
#cherrypy.tools.validate_user()
def index(self):
return analysis_panel.pick_data_sets(user_id=cherrypy.request.uid)
Or in your cherrypy config, enable that tool:
config = {
'/analyze': {
'tools.validate_user.on': True,
},
}
The function/method is defined in the class, it doesn't make sense to decorate it with an instance variable because it won't be the same decorator for each instance.
You may consider using a property to create the decorated method when it is accessed:
#property
def index(self):
#cherrypy.expose
#self.validuser(['uid'])
def wrapped_index(**kw):
return analysis_panel.pick_data_sets(user_id=kw['uid'])
return wrapped_index
You may also consider trying to apply lru_cache to save the method for each instance but I'm not sure how to apply that with the property.

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

How to test a function for different database states in Python and Django? (avoiding repetitions)

I wrote unit tests first, then I made all the tests pass, now I am looking how to refactor the code to avoid repetitions.
I have a function which returns different values depending on the context. All context is extracted on-the-fly from the Django models.
Currently my code is structured like that:
from django.test import TestCase
class MyTest(TestCase):
def test_case1(self):
user = User.objects.create(username='user')
tested_class = MyClass(user)
Model1.objects.create(...) # one type of context
self.assertEqual(...) # test the class method for this type of context
def test_case2(self):
user = User.objects.create(username='user')
tested_class = MyClass(user)
Model2.objects.create(...) # another type of context
self.assertEqual(...) # test the class method for this type of context
def test_case3(self):
user = User.objects.create(username='user')
tested_class = MyClass(user)
Model1.objects.create(...) # yet another type of context
Model2.objects.create(...)
self.assertEqual(...) # test the class method for this type of context
Obviously, the code is quite repetitive: the first two lines are the same in each function.
My first idea was to use a shared setup function:
def setUp(self):
self.user = User.objects.create(username='user')
self.tested_class = MyClass(user)
but this solution didn't work: all model updates were shared, and tests became dependent on each other.
What I need instead is a clean state ("empty database") before starting each test.
What else can I try?
Why don't you just destroy all the objects you don't want in your teardown? Looks like Django allows you to do this type of thing pretty easily.
def tearDown(self):
User.objects.all().delete()

mocking a function within a class method

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')

Categories