Search code examples
pythonpython-3.xdjangodjango-rest-frameworkpython-unittest

Mocking instance methods in python unittest


I'm a software developer for several years but new to python. I'm writing a unit test (so no database connection present) that involves a model in Django that accesses another model referenced via a foreign key connection. I want to mock the method that accesses this connection and replace the result with a hard coded response, that is different for each instance though.

Here's a minimal example:

### tests/test_MyTestCase.py

from unittest import TestCase
from djangoapi.models import *


class MyTestCase(TestCase):
    def setUp(self):
        self.instance1 = MyModel()
        self.instance2 = MyModel()

        foreignKey1 = MySubModel()
        foreignKey1.my_value = 1

        # Mock that self.instance1.submodel_set.all() returns [foreignKey1]
        
        foreignKey2 = MySubModel()
        foreignKey2.my_value = 2

        # Mock that self.instance2.submodel_set.all() returns [foreignKey2]

    def testSomething(self):
        self.assertEqual(self.instance1.get_max_value(), 1)
        self.assertEqual(self.instance2.get_max_value(), 2)

        
### models.py

from django.db import models

class MyModel(models.Model):
    def get_max_value(self):
        value = 0
        # the return value of self.submodel_set.all() is what I want to mock
        for model in self.submodel_set.all():
            value = max(value, model.my_value)
        return value


class Submodel(models.Model):
    my_model = models.ForeignKey(MyModel, null=True, on_delete=models.SET_NULL)
    my_value = models.IntegerField()

I tried several combinations of the @patch decorator, Mock() and MagicMock() but could not get it to work. Thank you in advance!


Solution

  • After some more research, I found out that the main problem was that the mocking can't happen in the setUp method, but must be done in the test methods themselves. This means some code duplication, but at least it works now. For reference, here's the working example:

    from unittest import TestCase
    from unittest.mock import patch
    from djangoapi.models import *
    
    
    class MyTestCase(TestCase):
        def setUp(self):
            self.my_model = MyModel()
    
            self.foreignKey1 = MySubModel()
            self.foreignKey1.my_value = 1
    
            self.foreignKey2 = MySubModel()
            self.foreignKey2.my_value = 2
    
        @patch('djangoapi.models.MyModel.submodel_set')
        def testSomething(self, mock_submodel_set):
            mock_submodel_set.all.return_value = [self.foreignKey1]
            self.assertEqual(self.my_model.get_max_value(), 1)
    
            mock_submodel_set.all.return_value = [self.foreignKey2]
            self.assertEqual(self.my_model.get_max_value(), 2)