Reputation: 13
A project I'm working on uses django for basically everything. When writing a model, I found it necessary to override the save() method to spin off a task to be run by a worker:
class MyModel(models.Model)
def _start_processing(self):
my_task.apply_async(args=['arg1', ..., 'argn'])
def save(self, *args, **kwargs):
"""Saves the model object to the database"""
# do some stuff
self._start_processing()
# do some more stuff
super(MyModel, self).save(*args, **kwargs)
In my tester, I want to test the parts of the save override that are designated by # do some stuff
and # do some more stuff
, but don't want to run the task. To do this, I believe I should be using mocking (which I'm very new to).
In my test class, I've set it up to skip the task invocation:
class MyModelTests(TestCase):
def setUp(self):
# Mock the _start_processing() method. Ha!
@patch('my_app.models.MyModel._start_processing')
def start_processing(self, mock_start_processing):
print('This is when the task would normally be run, but this is a test!')
# Create a model to test with
self.test_object = MyModelFactory()
Since the factory creates and saves an instance of the model, I need to have overwritten the _start_processing()
method before that is called. The above doesn't seem to be working (and the task runs and fails). What am I missing?
Upvotes: 1
Views: 107
Reputation: 4034
First of all, you have to wrap into decorator not the function which you want to use as replacement, but the "scope" in which your mock should work. So, for example, if you need to mock the _start_processing
for the whole MyModelTests
class, you should place the decorator before the class definition. If only for one test method - wrap only test method with it.
Secondly, define that start_processing
function somewhere outside the class, and pass @patch('my_app.models.MyModel._start_processing', new=start_processing)
, so it will know what to use as a replacement for actual method. But be aware to match the actual method signature, so use just
def start_processing(self):
print('This is when the task would normally be run, but this is a test!')
Thirdly, you will have to add mock_start_processing
argument to each test case inside this class (test_... methods), just because mocking works like this :).
And finally. You have to be aware about the target
you are patching. Your current my_app.models.MyModel._start_processing
could be broken. You have to patch the class using the path where it is USED, not where it is DEFINED. So, if you are creating objects with MyModelFactory
inside TestCase
, and MyModelFactory
lives in my_app.factories
and it imports MyModel
as from .models import MyModel
, you will have to use @patch('my_app.factories.MyModel._start_processing')
, not 'my_app.models.MyModel._start_processing'
.
Hopefully, it helps.
Upvotes: 1