2016-03-05 50 views
3

我一直在爲我的ModelForm編寫單元測試,它有一個ModelChoiceField。我使用模擬數據創建Form實例。UnitTest ModelForm具有模型數據的ModelChoiceField

這裏是我的模型:

# 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'squeryset?我試圖修補形式的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

回答

7

我會說打電話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.cleanMenuForm._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()) 

你需要問自己你的目標是與該考什麼如果你打算這樣做。

+0

使用'Model.objects.create'似乎是比使用'mock.Mock'更好的選擇。我的意思是,爲什麼要使用圖書館,如果沒有它,事情就可以輕鬆完成。 – xyres

0

如果認爲你的測試不是「單位」就夠了。你似乎想要測試價格門檻嗎?也許你可以做這樣的事情:

# 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返回一個簡單的比較,但如果你只是想測試價格門檻而不打擾Django內部的表單驗證過程,它不是很差的隔離它