Django Unit Test - using factory_boy build() on a Model with Many-To-Many relationship

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.

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

Back to Top