Django:如何编写对象创建的Mock/Patch测试?遇_state属性错误求助
解决Django模型测试中Mock/Patch导致的AttributeError和类型断言失败问题
我来帮你拆解一下你遇到的两个问题,一步步找到解决办法:
第一个错误:AttributeError: Mock object has no attribute '_state'
这个问题出在你用MagicMock(spec=Product)创建的product和til对象上。Django的ForeignKey字段在关联对象时,会检查关联对象是否是有效的Django模型实例——而所有Django模型实例都带有一个内部属性_state,用来跟踪对象的数据库状态(比如是否已保存、是否为新建)。
你的MagicMock虽然用了spec=Product,但它只是模拟了Product类的公共方法和属性,并没有包含Django模型内部的_state属性,所以当你把这个Mock对象传给Sale的ForeignKey字段时,Django会找不到_state而抛出错误。
第二个问题:patch Sale.objects后断言isinstance失败
当你patchapp.models.Sale.objects时,Sale.objects.create方法会被替换成一个Mock函数,默认情况下它返回的是一个Mock对象,而不是真正的Sale实例,所以isinstance(new_sale, Sale)自然会返回False。
解决方案
方案1:使用真实的模型实例(推荐,更贴近真实业务场景)
既然你用的是Django的TestCase,它会自动创建一个临时测试数据库,测试完成后自动销毁,所以完全可以创建真实的Product和Til实例来关联:
修改你的测试类代码:
from django.test import TestCase from django.utils import timezone from app.models import Sale, Product, Til import mock import datetime class SaleModelTestCase(TestCase): @classmethod def setUpClass(cls): super().setUpClass() # 创建真实的测试实例(根据你的Product/Til模型实际字段调整) cls.product = Product.objects.create(name="测试商品") cls.til = Til.objects.create(name="测试终端") cls.price = 2.34 def test_create_sale(self): now = timezone.now() # 注意:你的save方法用的是timezone.now(),所以要patch这个方法而非datetime with mock.patch('django.utils.timezone.now') as mock_now: mock_now.return_value = now new_sale = Sale.objects.create( from_product=self.product, from_til=self.til, price=self.price ) # 现在断言会成功 self.assertTrue(isinstance(new_sale, Sale)) self.assertEqual(new_sale.create_date, now)
方案2:如果一定要用Mock关联对象
如果你因为某些原因必须用Mock来模拟Product和Til,那需要给Mock对象手动添加_state属性,模拟Django模型实例的内部结构:
from django.test import TestCase from django.utils import timezone from app.models import Sale import mock from unittest.mock import MagicMock import datetime class SaleModelTestCase(TestCase): @classmethod def setUpClass(cls): super().setUpClass() cls.product = MagicMock(spec=Product) # 添加Django模型必需的内部_state属性 cls.product._state = MagicMock() cls.til = MagicMock(spec=Til) cls.til._state = MagicMock() cls.price = 2.34 def test_create_sale(self): now = timezone.now() with mock.patch('django.utils.timezone.now') as mock_now: mock_now.return_value = now new_sale = Sale.objects.create( from_product=self.product, from_til=self.til, price=self.price ) self.assertTrue(isinstance(new_sale, Sale)) self.assertEqual(new_sale.create_date, now)
额外说明
- 你之前patch的是
django.utils.datetime_safe.datetime,但你的Sale模型save方法里用的是timezone.now(),所以应该patchdjango.utils.timezone.now才会生效,这也是之前你的create_date断言可能不会正确的原因之一。 - 尽量避免patch模型的
objects管理器,除非你有特殊需求(比如测试管理器的自定义方法),测试模型的save逻辑时,使用真实的实例创建更可靠。
内容的提问来源于stack exchange,提问作者user7120561
相关产品推荐
相关产品推荐

