Mock a model method in Django

Darwin Tech picture Darwin Tech · Nov 7, 2013 · Viewed 9.3k times · Source

I'm just getting started with Mock for testing Django apps without touching the db. I can successfully mock my model objects in my tests, but I'm a little confused as to how to deal with model methods which involve objects.

As an example I have this in my models.py:

class Skill(models.Model):
   title = models.CharField(max_length=255)
   category = models.ForeignKey(
       SkillCategory, default=None, null=True, blank=True
   )

def __unicode__(self):
    return self.title

class UserProfile(models.Model):
    user = models.OneToOneField(User)
    skill = models.ManyToManyField(Skill, default=None)
    avatar = models.URLField(
        max_length=400, default=None, null=True, blank=True
    )

    def __unicode__(self):
        return self.user.username

    def update_skills(self, cleaned_skills):
        for s in cleaned_skills:
            skill, created = Skill.objects.get_or_create(title=s)
            self.skill.add(skill)

So far in my re-factored tests I have done this:

class ProfileTestCase(unittest.TestCase):
    def setUp(self):
        # mock user
        self.user = mock.Mock(spec=User)
        self.user.username = "john"
        self.user.email = "[email protected]"
        self.user.password = "pass"
        # mock profile
        self.userprofile = mock.Mock(spec=UserProfile)
        self.userprofile.user = self.user
        # mock skill category
        self.skill_category = mock.Mock(spec=SkillCategory)
        self.skill_category.title = "foo"
        # mock skill
        self.skill = mock.Mock(spec=Skill)
        self.skill.title = "ninja"
        self.skill.category = self.skill_category

    def test_skillcategory_created(self):
        self.assertEqual(self.skill.category.title, 'foo')

    def test_skill_created(self):
        self.assertEqual(self.skill.title, 'ninja')

    ...
    # and so on

In my old tests, which use the db, I had this to test my model method:

    ...

    def test_update_skills(self):
        cleaned_skills = ['mysql', 'unix']
        self.userprofile.update_skills(cleaned_skills)
        skills = self.userprofile.skill.values_list('title', flat=True)
        self.assertTrue('mysql' in skills)

I'm scratching my head on how I should refactor this line:

self.userprofile.update_skills(cleaned_skills)

to take advantage of Mock.

Answer

nanvel picture nanvel · May 13, 2014
  1. Your test looks strange
  2. You can test model methods without creating records in database, just don't use .create() or .save():

    def test_my_method(self):
        my_model = MyModel(someattr='someval', ...)
        self.assertEqual(my_model.my_method(), ...)
    
  3. Sometimes it is good decision to mock .save() method to disable creating records in database and speedup tests. See few examples of how You can use mock in tests: http://nanvel.name/2014/04/testing-django-application#patchmock