You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 07:57:20