Django ModelForm与mock.patch.object破坏单元测试隔离问题排查
Django模型字段Mock失效问题:原因与解决方案
问题原因
你遇到的问题核心是Django ModelForm的字段缓存机制在搞鬼:
当你实例化MyExampleForm时,Django会把模型的字段元数据(包括event_datetime字段的default属性)缓存起来,而且这个缓存是全局共享的。测试套件里的所有测试都会复用同一个模型类和Form类实例,一旦Form被初始化过,后续你对字段default的patch操作就无法影响到已经被缓存的字段配置了——这就是为什么单独运行ExampleTest没问题,但和ExampleFormTest一起运行时,mock就失效了。
解决方案
这里给你三个可行的方案,按推荐程度排序:
1. 直接Mock timezone.now(最推荐)
既然event_datetime的默认值是timezone.now,不如直接mock这个底层函数,跳过字段缓存的问题。这样不管是模型实例化还是Form使用,都会调用你mock后的函数,从根源上避免缓存干扰:
class ExampleTest(TestCase): def test_init_date_missing(self): """Tests that date is set automatically when not provided""" test_dt = datetime(2019, 2, 10, 10, 10, 10) # 直接mock timezone.now函数 with mock.patch('django.utils.timezone.now', return_value=test_dt): actual = MyExampleModel() self.assertEqual(actual.event_datetime, test_dt) actual.save()
2. 清除ModelForm的字段缓存
如果你坚持要mock字段的default属性,可以在每个测试后清除Form的缓存,确保下一个测试使用的是全新的字段配置:
class ExampleFormTest(TestCase): def tearDown(self): # 清除Form的字段缓存 if hasattr(MyExampleForm._meta, 'fields'): del MyExampleForm._meta.fields if hasattr(MyExampleForm._meta, '_fields_cache'): del MyExampleForm._meta._fields_cache def test_form(self): MyExampleForm()
或者更彻底一点,每次测试都动态创建ModelForm,避免使用全局定义的Form类:
from django.forms.models import modelform_factory class ExampleFormTest(TestCase): def test_form(self): # 动态生成ModelForm,不会留下全局缓存 MyDynamicForm = modelform_factory(MyExampleModel, fields=['event_datetime']) MyDynamicForm()
3. 调整测试执行顺序(不推荐)
如果测试套件是按字母顺序执行的(比如ExampleFormTest先于ExampleTest运行),你可以修改测试类名或者使用测试框架的排序功能,让ExampleTest先运行。但这种方法依赖测试执行顺序,会破坏测试的独立性,一旦后续测试结构变化很容易出问题,所以不推荐。
内容的提问来源于stack exchange,提问作者chrisbunney
相关产品推荐
相关产品推荐

