Hanny
Hanny

Reputation: 682

Django Model Mocking - Better to create model or mock model for testing?

I have a very simple model like this:

class Observation(models.Model):
    thing = models.ForeignKey(Thing, on_delete=models.PROTECT)
    user_locked = models.BooleanField(default=False)
    admin_locked = models.BooleanField(default=False)
    created = models.DateTimeField(auto_now=False, auto_now_add=True)
    created_by = models.ForeignKey(
        User, on_delete=models.PROTECT, related_name="%(class)s_created_by"
    )
    updated = models.DateTimeField(auto_now=True, auto_now_add=False)
    updated_by = models.ForeignKey(
        User, on_delete=models.PROTECT, related_name="%(class)s_updated_by"
    )

    def is_fully_locked(self):
        """Return boolean if Observation is locked by both"""
        if self.user_locked and self.admin_locked:
            return True
        return False

I'm trying to test the is_fully_locked() method and I was curious about creating the Observation model.

I have used things like Mixer and factory_boy in the past to create objects to test against, but as those projects grew so did the time it took for the tests to run which I'd like to avoid. I have recently started reading about using Mock (I'm still confused by it a little).

My question is - for a simple model like this - what is the best/fastest way to test that method?

Currently I have some test like this:

class ObservationTest(TestCase):
    def test_is_fully_locked_with_no_employee_locked(self):
        start = time.time()
        observation = Observation(admin_locked=True)
        print(time.time() - start)
        # Takes 5.91278076171875e-05
        # FASTEST
        self.assertFalse(observation.is_fully_locked())

    def test_is_fully_locked_with_no_employee_locked_mixer(self):
        start = time.time()
        observation = mixer.blend(Observation, admin_locked=True)
        print(time.time() - start)
        # Takes 0.011276006698608398
        # SLOWEST
        self.assertFalse(observation.is_fully_locked())

    def test_is_fully_locked_with_no_employee_locked_mock(self):
        start = time.time()
        observation = mock.Mock(spec=Observation)
        observation.admin_locked = True
        print(time.time() - start)
        # Takes 0.0005071163177490234
        # SECOND FASTEST - but errors out
        self.assertFalse(observation.is_fully_locked())
        # This assertion fails: AssertionError: <Mock name='mock.is_fully_locked()' id='4545690352'> is not false

I am also curious - are these each valid ways to test this? I know the mixer method works - but it touches the database, which I believe you're supposed to avoid (and it's probably why it's the slowest).

Questions are as follows:

Upvotes: 0

Views: 477

Answers (1)

Alex Vyushkov
Alex Vyushkov

Reputation: 670

I'd say all of these approaches are acceptable (however, you probably don't want to mock the object you are testing like you do in test_is_fully_locked_with_no_employee_locked_mock - rather create Observation object and mock admin_locked field).

I'm not sure if you are supposed to avoid "touching" the database - Django test runner creates a test database for each run. It is a bit slower, but in some cases that's the only way to test your code. If you can get away with in-memory object (like test_is_fully_locked_with_no_employee_locked test), it can speed up your test.

Mock library is usually used to mock functions that, for example, make a request to external server, run heavy database query, non-deterministic and so on - that are hard to reproduce in test environment.

Upvotes: 2

Related Questions