I’m working on writing unit tests for a DRF project using pytest and factory_boy. I’m running into issues with many-to-many relationships. Specifically, when I try to use .build() in my unit tests, DRF attempts to access the M2M field which requires a saved object, leading to errors.
tests_serializers.py
def test_serialize_quality_valid_data(self):
user = UserFactory.build()
quality = QualityFactory.build(created_by=user)
serializer = QualitySerializer(quality)
data = serializer.data
assert data["num"] == quality.num
error:
FAILED quality/tests/tests_serializers.py::TestQualitySerializer::test_serialize_quality_valid_data - ValueError: "<Quality: Quality object (None)>" needs to have a value for field "id" before this many-to-many relationship can be used.
model.py
class QualityTag(ExportModelOperationsMixin("quality_tag"), models.Model):
name = models.CharField(max_length=64, unique=True)
description = models.TextField()
class Quality(ExportModelOperationsMixin("quality"), models.Model):
num = models.IntegerField()
title = models.CharField(max_length=64)
...
tags = models.ManyToManyField(QualityTag, related_name="qualities", blank=True)
factories.py
class QualityTagFactory(DjangoModelFactory):
class Meta:
model = QualityTag
name = factory.Sequence(lambda n: f"Quality Tag {n}")
class QualityFactory(factory.django.DjangoModelFactory):
class Meta:
model = Quality
num = factory.Faker("random_int", min=1, max=999)
@factory.post_generation
def tags(self, create, extracted, **kwargs):
if not create:
return
if extracted:
for tag in extracted:
self.tags.add(tag)
serializers.py
class QualitySerializer(serializers.ModelSerializer):
tags = QualityTagDetailSerializer(many=True)
created_by = UserProfileSerializer()
updated_by = UserProfileSerializer()
class Meta: model = Quality
fields = "__all__"
read_only_fields = ["quality_num", "tags", "created_by", "updated_by"]
I’ve been advised to switch to .create() instead of .build(), but I’d prefer to keep this as a pure unit test if possible (I believe it may become an integration test if I use build()?) I have tried converting it to using factory_boy .create(), however, I want to keep it as a simple unit test.
I have tried converting it to using factory_boy .create(), however, I want to keep it as a simple unit test.
1 Answer 1
As I know when you use factory_boy.build(), the object exists only in memory without a database ID. Django's M2M fields require a saved object (with an ID) to function properly. So, you can test your serializer logic without hitting the database by mocking the M2M field, something like this:
def test_serialize_quality_with_tags(self):
user = UserFactory.build()
quality = QualityFactory.build(created_by=user)
# Create tag objects in memory
tag1 = QualityTagFactory.build(name="Tag 1")
tag2 = QualityTagFactory.build(name="Tag 2")
mock_tags_data = [tag1, tag2]
with patch.object(quality, 'tags') as mock_tags:
# Mock the M2M field to return our test data
mock_tags.all.return_value = mock_tags_data
mock_tags.__iter__ = lambda x: iter(mock_tags_data)
serializer = QualitySerializer(quality)
data = serializer.data
assert data["num"] == quality.num
assert len(data["tags"]) == 2
assert data["tags"][0]["name"] == "Tag 1"
check this for more details: https://factoryboy.readthedocs.io/en/stable/reference.html#id9
Comments
Explore related questions
See similar questions with these tags.