Reputation: 2231
I am writing tests for a website I'm working on and I'm representing the models with factoryboy
Factory objects.
However, I've run into some behavior I found somewhat confusing and I was wondering if anyone here would be so kind to explain it to me
I'm running a test that contains the following model:
STATUS = (
('CALCULATING'),
('PENDING'),
('BUSY'),
('SUCCESS'),
('FAILED')
)
class SchoolImport(models.Model):
date = models.DateTimeField(auto_now_add=True)
status = models.CharField(
verbose_name=_('status'), choices=STATUS,
max_length=50, default='CALCULATING'
)
For which I've created the following factory. As you can see the status
is set to its default value, which I found more realistic than having a randomly selected value
class SchoolImportFactory(factory.DjangoModelFactory):
class Meta:
model = models.SchoolImport
status = 'CALCULATING'
school = factory.SubFactory(SchoolFactory)
@factory.lazy_attribute
def date(self):
return timezone.now() - datetime.timedelta(days=10)
Below you'll see both a (simplified) version of the function that is being tested, as well as the test itself. (I've currently commented out all other code on my laptop, so the function that you see below is an accurate representation)
The gist of it is that the function receives an id
value that it will use to fetch an SchoolImport
object from the database and change its status. The function will be run in celery and thus returns nothing.
When I run this test through the debugger I can see that the value is changed correctly. However, when the test runs its final assertion it fails as self.school_import.status
is still equal to CALCULATING
.
#app.utils.py
def process_template_objects(school_import_pk):
school_import = models.SchoolImport.objects.get(id=import_file_pk)
school_import.status = 'BUSY'
school_import.save()
#app.tests.test_utils.py
class Test_process_template_objects_function(TestCase):
def setUp(self):
self.school = SchoolFactory()
self.school_import = SchoolImportFactory(
school=self.school
)
def test_function_alters_school_import_status(self):
self.assertEqual(
self.school_import.status, 'CALCULATING'
)
utils.process_template_objects(self.school_import.id)
self.assertNotEqual(
self.school_import.status, 'CALCULATING'
)
When I run this test through a debugger (with a breakpoint just before the failing assertion) and run SchoolImport.objects.get(id=self.school_import.id).status
it does return the correct BUSY
value.
So though the object being represented by the FactoryInstance is being updated correctly, the changes are not reflected in the factory instance itself.
Though I realize I'm probably doing something wrong here / encountering expected behavior, I was wondering how people who write tests using factoryboy fget around this behavior, or if perhaps there was a way to 'refresh' the factoryboy instance to reflect changes to the model instance.
Upvotes: 1
Views: 2312
Reputation: 91
If you delete a field from a model instance, accessing it again reloads the value from the database.
obj = MyModel.objects.first()
del obj.field
obj.field # Loads the field from the database
See https://docs.djangoproject.com/en/2.2/ref/models/instances/#refreshing-objects-from-database
Upvotes: 1
Reputation: 3589
The issue comes from the fact that, in process_template_objects
, you work with a different instance of the SchoolImport
object than the one in the test.
If you run:
a = models.SchoolImport.objects.get(pk=1)
b = models.SchoolImport.objects.get(pk=2)
assert a == b # True: both refer to the same object in the database
assert a is b # False: different Python objects, each with its own memory
a.status = 'SUCCESS'
a.save()
assert a.status == 'SUCCESS' # True: it was indeed changed in this Python object
assert b.status == 'SUCCESS' # False: the 'b' object hasn't seen the change
In order to fix this, you should refetch the instance from the database after calling process_template_objects
:
utils.process_template_objects(self.school_import.id)
self.school_import.refresh_from_db()
See https://docs.djangoproject.com/en/2.2/ref/models/instances/#refreshing-objects-from-database for a more detailed explanation!
Upvotes: 4