具有 ModelChoiceField 和模拟数据的 UnitTest ModelForm
UnitTest ModelForm having ModelChoiceField with Mock Data
我一直在尝试为我的 ModelForm 编写单元测试,它有一个 ModelChoiceField。我正在使用模拟数据创建 Form 实例。
这是我的模型:
# models.py
class Menu(models.Model):
dish = models.ForeignKey(Dish, default=None)
price = models.DecimalField(max_digits=7, decimal_places=2)
# forms.py
class MenuForm(forms.ModelForm):
class Meta:
model = Menu
fields = ('dish', 'price',)
def clean(self):
cleaned_data = super(MenuForm, self).clean()
price = cleaned_data.get('price', None)
dish = cleaned_data.get('dish', None)
# Some validation below
if price < 70:
self.add_error('price', 'Min price threshold')
return cleaned_data
这是我的测试用例:
class MenuFormTest(TestCase):
def test_price_threshold(self):
mock_dish = mock.Mock(spec=Dish)
form_data = {
'dish': mock_dish,
'price': 80,
}
form = forms.MenuForm(data=form_data)
self.assertTrue(form.is_valid())
失败并出现以下错误:
<ul class="errorlist"><li>dish<ul class="errorlist"><li>Select a valid choice. That choice is not one of the available choices.</li></ul></li></ul>
如何避免抛出该错误。 form.is_valid()
应该 True
在那里。有什么方法可以修补 ModelChoiceField's
queryset
?我尝试修补表单的 dish
字段 clean()
方法,如下所示:
form = forms.MenuForm(data=form_data)
dish_clean_patcher = mock.patch.object(form.fields['dish'], 'clean')
dish_clean_patch = dish_clean_patcher.start()
dish_clean_patch.return_value = mock_dish
self.assertTrue(form.is_valid())
然后看起来,在 _post_clean()
方法中将表单数据保存到模型实例时失败了。这是回溯:
Traceback (most recent call last):
File "/home/vagrant/venv/local/lib/python2.7/site-packages/mock/mock.py", line 1305, in patched
return func(*args, **keywargs)
File "/vagrant/myapp/tests/test_forms.py", line 51, in test_price_threshold
self.assertFalse(form.is_valid())
File "/home/vagrant/venv/local/lib/python2.7/site-packages/django/forms/forms.py", line 185, in is_valid
return self.is_bound and not self.errors
File "/home/vagrant/venv/local/lib/python2.7/site-packages/django/forms/forms.py", line 177, in errors
self.full_clean()
File "/home/vagrant/venv/local/lib/python2.7/site-packages/django/forms/forms.py", line 396, in full_clean
self._post_clean()
File "/home/vagrant/venv/local/lib/python2.7/site-packages/django/forms/models.py", line 427, in _post_clean
self.instance = construct_instance(self, self.instance, opts.fields, construct_instance_exclude)
File "/home/vagrant/venv/local/lib/python2.7/site-packages/django/forms/models.py", line 62, in construct_instance
f.save_form_data(instance, cleaned_data[f.name])
File "/home/vagrant/venv/local/lib/python2.7/site-packages/django/db/models/fields/__init__.py", line 874, in save_form_data
setattr(instance, self.name, data)
File "/home/vagrant/venv/local/lib/python2.7/site-packages/django/db/models/fields/related.py", line 632, in __set__
instance._state.db = router.db_for_write(instance.__class__, instance=value)
File "/home/vagrant/venv/local/lib/python2.7/site-packages/django/db/utils.py", line 300, in _route_db
if instance is not None and instance._state.db:
File "/home/vagrant/venv/local/lib/python2.7/site-packages/mock/mock.py", line 716, in __getattr__
raise AttributeError("Mock object has no attribute %r" % name)
AttributeError: Mock object has no attribute '_state'
我该如何避免那部分?我根本不希望它查看 instance._state.db
。
我是否正确地测试了表格?或者我应该不调用 form.is_valid()
,而是调用 form.clean()
方法,完全修补 super(MenuForm, self).clean()
方法,然后检查 form.errors
?
我会说调用 form.is_valid()
是测试表单的好方法。不过,我不确定是否要模拟模型。
Internally the form is calling get_limit_choices_to
on your dish
field(Django 当前正在为您创建)。
You would need to mock the dish
field's .queryset
or get_limit_choices_to
here,(或调用堆栈中使此处的值变得毫无意义的其他地方)以某种方式实现您想要的。
或者,在你的测试中创建一个 Dish
并让 Django 的内部继续做他们正在做的事情会简单得多。
class MenuFormTest(TestCase):
def test_price_threshold(self):
dish = Dish.objects.create(
# my values here
)
form_data = {
'dish': dish.id,
'price': 80,
}
form = MenuForm(data=form_data)
self.assertTrue(form.is_valid())
如果您真的不想使用 Django 的测试数据库,一种策略可能是模拟 MenuForm.clean
和 MenuForm._post_clean
:
class MenuFormTest(TestCase):
def test_price_threshold(self):
mock_dish = mock.Mock(spec=Dish)
form_data = {
'dish': 1,
'price': 80,
}
form = MenuForm(data=form_data)
form.fields['dish'].clean = lambda _: mock_dish
form._post_clean = lambda : None
self.assertTrue(form.is_valid())
如果你要这样做,你需要问问自己这个测试的目标是什么。
如果觉得你的测试还不够"unit"。您似乎想测试价格门槛,对吗?也许你可以做类似的事情:
# forms.py
class MenuForm(forms.ModelForm):
class Meta:
model = Menu
fields = ('dish', 'price',)
def clean(self):
cleaned_data = super(MenuForm, self).clean()
price = cleaned_data.get('price', None)
dish = cleaned_data.get('dish', None)
# Some validation below
if not self._is_price_valid(price):
self.add_error('price', 'Min price threshold')
return cleaned_data
def _is_price_valid(self, price):
return price >= 70
还有你的测试:
class MenuFormTest(TestCase):
def test_price_threshold(self):
form = forms.MenuForm()
self.assertTrue(form._is_price_valid(80))
我同意,对于此示例,仅添加一个方法 ta returns 有点 "overkill" 一个简单的比较,但如果您只是想测试价格阈值而不用担心内部表单验证过程对于 Django 来说,隔离它也不错
我一直在尝试为我的 ModelForm 编写单元测试,它有一个 ModelChoiceField。我正在使用模拟数据创建 Form 实例。
这是我的模型:
# models.py
class Menu(models.Model):
dish = models.ForeignKey(Dish, default=None)
price = models.DecimalField(max_digits=7, decimal_places=2)
# forms.py
class MenuForm(forms.ModelForm):
class Meta:
model = Menu
fields = ('dish', 'price',)
def clean(self):
cleaned_data = super(MenuForm, self).clean()
price = cleaned_data.get('price', None)
dish = cleaned_data.get('dish', None)
# Some validation below
if price < 70:
self.add_error('price', 'Min price threshold')
return cleaned_data
这是我的测试用例:
class MenuFormTest(TestCase):
def test_price_threshold(self):
mock_dish = mock.Mock(spec=Dish)
form_data = {
'dish': mock_dish,
'price': 80,
}
form = forms.MenuForm(data=form_data)
self.assertTrue(form.is_valid())
失败并出现以下错误:
<ul class="errorlist"><li>dish<ul class="errorlist"><li>Select a valid choice. That choice is not one of the available choices.</li></ul></li></ul>
如何避免抛出该错误。 form.is_valid()
应该 True
在那里。有什么方法可以修补 ModelChoiceField's
queryset
?我尝试修补表单的 dish
字段 clean()
方法,如下所示:
form = forms.MenuForm(data=form_data)
dish_clean_patcher = mock.patch.object(form.fields['dish'], 'clean')
dish_clean_patch = dish_clean_patcher.start()
dish_clean_patch.return_value = mock_dish
self.assertTrue(form.is_valid())
然后看起来,在 _post_clean()
方法中将表单数据保存到模型实例时失败了。这是回溯:
Traceback (most recent call last):
File "/home/vagrant/venv/local/lib/python2.7/site-packages/mock/mock.py", line 1305, in patched
return func(*args, **keywargs)
File "/vagrant/myapp/tests/test_forms.py", line 51, in test_price_threshold
self.assertFalse(form.is_valid())
File "/home/vagrant/venv/local/lib/python2.7/site-packages/django/forms/forms.py", line 185, in is_valid
return self.is_bound and not self.errors
File "/home/vagrant/venv/local/lib/python2.7/site-packages/django/forms/forms.py", line 177, in errors
self.full_clean()
File "/home/vagrant/venv/local/lib/python2.7/site-packages/django/forms/forms.py", line 396, in full_clean
self._post_clean()
File "/home/vagrant/venv/local/lib/python2.7/site-packages/django/forms/models.py", line 427, in _post_clean
self.instance = construct_instance(self, self.instance, opts.fields, construct_instance_exclude)
File "/home/vagrant/venv/local/lib/python2.7/site-packages/django/forms/models.py", line 62, in construct_instance
f.save_form_data(instance, cleaned_data[f.name])
File "/home/vagrant/venv/local/lib/python2.7/site-packages/django/db/models/fields/__init__.py", line 874, in save_form_data
setattr(instance, self.name, data)
File "/home/vagrant/venv/local/lib/python2.7/site-packages/django/db/models/fields/related.py", line 632, in __set__
instance._state.db = router.db_for_write(instance.__class__, instance=value)
File "/home/vagrant/venv/local/lib/python2.7/site-packages/django/db/utils.py", line 300, in _route_db
if instance is not None and instance._state.db:
File "/home/vagrant/venv/local/lib/python2.7/site-packages/mock/mock.py", line 716, in __getattr__
raise AttributeError("Mock object has no attribute %r" % name)
AttributeError: Mock object has no attribute '_state'
我该如何避免那部分?我根本不希望它查看 instance._state.db
。
我是否正确地测试了表格?或者我应该不调用 form.is_valid()
,而是调用 form.clean()
方法,完全修补 super(MenuForm, self).clean()
方法,然后检查 form.errors
?
我会说调用 form.is_valid()
是测试表单的好方法。不过,我不确定是否要模拟模型。
Internally the form is calling get_limit_choices_to
on your dish
field(Django 当前正在为您创建)。
You would need to mock the dish
field's .queryset
or get_limit_choices_to
here,(或调用堆栈中使此处的值变得毫无意义的其他地方)以某种方式实现您想要的。
或者,在你的测试中创建一个 Dish
并让 Django 的内部继续做他们正在做的事情会简单得多。
class MenuFormTest(TestCase):
def test_price_threshold(self):
dish = Dish.objects.create(
# my values here
)
form_data = {
'dish': dish.id,
'price': 80,
}
form = MenuForm(data=form_data)
self.assertTrue(form.is_valid())
如果您真的不想使用 Django 的测试数据库,一种策略可能是模拟 MenuForm.clean
和 MenuForm._post_clean
:
class MenuFormTest(TestCase):
def test_price_threshold(self):
mock_dish = mock.Mock(spec=Dish)
form_data = {
'dish': 1,
'price': 80,
}
form = MenuForm(data=form_data)
form.fields['dish'].clean = lambda _: mock_dish
form._post_clean = lambda : None
self.assertTrue(form.is_valid())
如果你要这样做,你需要问问自己这个测试的目标是什么。
如果觉得你的测试还不够"unit"。您似乎想测试价格门槛,对吗?也许你可以做类似的事情:
# forms.py
class MenuForm(forms.ModelForm):
class Meta:
model = Menu
fields = ('dish', 'price',)
def clean(self):
cleaned_data = super(MenuForm, self).clean()
price = cleaned_data.get('price', None)
dish = cleaned_data.get('dish', None)
# Some validation below
if not self._is_price_valid(price):
self.add_error('price', 'Min price threshold')
return cleaned_data
def _is_price_valid(self, price):
return price >= 70
还有你的测试:
class MenuFormTest(TestCase):
def test_price_threshold(self):
form = forms.MenuForm()
self.assertTrue(form._is_price_valid(80))
我同意,对于此示例,仅添加一个方法 ta returns 有点 "overkill" 一个简单的比较,但如果您只是想测试价格阈值而不用担心内部表单验证过程对于 Django 来说,隔离它也不错