0

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.

asked Sep 15 at 16:00

1 Answer 1

0

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

answered Sep 21 at 15:49
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.