'Unit Testing Django Model Save Function

I'm creating tests to check that a custom calibration model save function updates an asset record (foreign key) if it is the latest calibration record for the asset. The save function performs exactly as expected in live dev & production server and even in the django shell, but appears to fail during testing...

models.py

class Asset(models.Model):
    ...
    requires_calibration = models.BooleanField()
    passed_calibration = models.BooleanField(default=False)
    calibration_date_prev = models.DateField(null=True, blank=True)
    calibration_date_next = models.DateField(null=True, blank=True)


class CalibrationRecord(models.Model):
    calibration_record_id = models.AutoField(primary_key=True)
    asset = models.ForeignKey(
                              "myapp.Asset",
                              on_delete=models.CASCADE,
                              limit_choices_to={"requires_calibration": True}
                              )
    calibration_date = models.DateField(default=timezone.now)
    calibration_date_next = models.DateField(null=True, blank=True)
    calibration_outcome = models.CharField(max_length=10, default="Pass")

    def save(self, *args, **kwargs):

        super(CalibrationRecord, self).save(*args, **kwargs)

        # Check if this is the latest calibration record for any asset, if so update asset.calibration_dates and status
        latest_asset_calibration = CalibrationRecord.objects.filter(asset=self.asset.pk).order_by(
            "-calibration_date", "-calibration_record_id")[0]

        if self.pk == latest_asset_calibration.pk:

            Asset.objects.filter(pk=self.asset.pk).update(calibration_date_prev=self.calibration_date)

            if self.calibration_date_next:
                Asset.objects.filter(pk=self.asset.pk).update(calibration_date_next=self.calibration_date_next)
            else:
                Asset.objects.filter(pk=self.asset.pk).update(calibration_date_next=None)

            if self.calibration_outcome == "Pass":
                Asset.objects.filter(pk=self.asset.pk).update(passed_calibration=True)
            else:
                Asset.objects.filter(pk=self.asset.pk).update(passed_calibration=False)

tests_models.py example failing test

class CalibrationRecordTests(TestCase):
    def test_calibration_record_updates_asset_cal_date_prev(self):
        """
        All calibration records should update related Asset record's "calibration_date_prev" to calibration_date
        """
        asset1 = Asset.objects.create(asset_description="Test Asset 2", requires_calibration=True)
        self.assertIsNone(asset1.calibration_date_prev)
        cal = CalibrationRecord.objects.create(asset=asset1, calibration_description="Test Calibration 2", calibration_date=timezone.now())
        self.assertEqual(cal.calibration_date, asset1.calibration_date_prev)

Error log

======================================================================
FAIL: test_calibration_record_updates_asset_cal_date_prev (assetregister.tests_models.CalibrationRecordTests)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "C:\[path]\app\tests_models.py", line 159, in test_calibration_record_updates_asset_cal_date_prev
self.assertEqual(cal.calibration_date, asset1.calibration_date_prev)
AssertionError: datetime.datetime(2018, 2, 26, 12, 26, 34, 457513, tzinfo=<UTC>) != None
======================================================================

All of my tests relating to this custom calibration record save function appear to fail because the related asset record isn't updated when it should be.

Any ideas why this would work during dev and production but not during testing?

Even though the .create() method should automatically do a .save() after, I even tried doing a manual .save() after creating the calibration record, but it still seems to fail.



Solution 1:[1]

Solved!

Custom save functions correctly update the database, but not the model instance being tested! Need to refresh the model instance to get any updates by calling it again with something like [model].objects.get()

e.g.:

    asset = Asset.objects.create(asset_description="Test Asset 2", requires_calibration=True)
    self.assertIsNone(asset.calibration_date_prev)
    CalibrationRecord.objects.create(asset=asset, calibration_description="Test Calibration 2",
                                     calibration_date=timezone.now())
    cal = CalibrationRecord.objects.get(calibration_description="Test Calibration 2")
    asset = Asset.objects.get(asset_description="Test Asset 2")
    self.assertEqual(cal.calibration_date, asset.calibration_date_prev)

Solution 2:[2]

If you find yourself here, you'd be better off reloading the object with Model.refresh_from_db(using=None, fields=None). See the Django documentation

Also checkout Reload django object from database

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 Cowen Shears