How to use the Mock library to offset the Django ForeignKey value?

I have a model, and I am trying to test validation without calling the database level. Instead of describing the words, I will simply post some code examples. The problem here is that ForeignKey’s attitude towards Bar, which is not related to what I'm trying to check, but stops me from running the test I want.

myapp/models.py :

 from django.core.exceptions import ValidationError from django.db import models class BadFooError(ValidationError): pass class Bar(models.Model): description = models.CharField(max_length=20) class Foo(models.Model): bar = models.ForeignKey(Bar) a_value = models.IntegerField() b_value = models.BooleanField() def clean(self): super(Foo, self).clean() if self.b_value and self.a_value > 50: raise BadFooError("No good") 

Next, myapp/tests.py :

 from unittest import TestCase from mock import MagicMock from . import models class SimpleTest(TestCase): def test_avalue_bvalue_validation(self): foo = models.Foo() foo.a_value = 30 foo.b_value = True foo.bar = MagicMock(spec=models.Bar) self.assertRaises(models.BadFooError, foo.full_clean) def test_method_2(self): foo = models.Foo() foo.a_value = 30 foo.b_value = True foo.bar = MagicMock() foo.__class__ = models.Bar self.assertRaises(models.BadFooError, foo.full_clean) def test_method_3(self): foo = models.Foo() foo.a_value = 30 foo.b_value = True # ignore it and it will go away ...?? self.assertRaises(models.BadFooError, foo.full_clean) 

Finally, the output of python manage.py test myapp

 EEE ====================================================================== ERROR: test_avalue_bvalue_validation (myapp.tests.SimpleTest) ---------------------------------------------------------------------- Traceback (most recent call last): File "~/sandbox/myapp/tests.py", line 14, in test_avalue_bvalue_validation foo.bar = MagicMock(spec=models.Bar) File "~/dsbx/local/lib/python2.7/site-packages/django/db/models/fields/related.py", line 408, in __set__ instance._state.db = router.db_for_write(instance.__class__, instance=value) File "~/dsbx/local/lib/python2.7/site-packages/django/db/utils.py", line 142, in _route_db return hints['instance']._state.db or DEFAULT_DB_ALIAS File "~/dsbx/local/lib/python2.7/site-packages/mock.py", line 658, in __getattr__ raise AttributeError("Mock object has no attribute %r" % name) AttributeError: Mock object has no attribute '_state' ====================================================================== ERROR: test_method_2 (myapp.tests.SimpleTest) ---------------------------------------------------------------------- Traceback (most recent call last): File "~/sandbox/myapp/tests.py", line 21, in test_method_2 foo.bar = MagicMock() File "~/dsbx/local/lib/python2.7/site-packages/django/db/models/fields/related.py", line 405, in __set__ self.field.name, self.field.rel.to._meta.object_name)) ValueError: Cannot assign "<MagicMock id='31914832'>": "Foo.bar" must be a "Bar" instance. ====================================================================== ERROR: test_method_3 (myapp.tests.SimpleTest) ---------------------------------------------------------------------- Traceback (most recent call last): File "~/sandbox/myapp/tests.py", line 29, in test_method_3 self.assertRaises(models.BadFooError, foo.full_clean) File "/usr/lib/python2.7/unittest/case.py", line 471, in assertRaises callableObj(*args, **kwargs) File "~/dsbx/local/lib/python2.7/site-packages/django/db/models/base.py", line 926, in full_clean raise ValidationError(errors) ValidationError: {'bar': [u'This field cannot be null.']} ---------------------------------------------------------------------- Ran 3 tests in 0.003s FAILED (errors=3) Creating test database for alias 'default'... Destroying test database for alias 'default'... 

So my question is ... wat do?

+6
source share
2 answers

In my unit tests, I simply assign _state to a new Mock instance, as in this small change to your first unit test example:

 def test_avalue_bvalue_validation(self): foo = models.Foo() foo.a_value = 30 foo.b_value = True bar = Mock(spec=models.Bar) bar._state = Mock() foo.bar = bar self.assertRaises(models.BadFooError, foo.full_clean) 

However, to check your check as a black box, I would pull the check code into a separate method of your model, which I would call from the clean() method. You can then use unit test to use this verification code. You still need to do the assignment _stage = Mock() so that you can create your Foo instance, but at least you will minimize the calls in Django.

+4
source

Ok, now I just switched my argument to self.assertRaises(models.BadFooError, foo.clean) (the difference is foo.clean instead of foo.full_clean ). Although this works, it does not seem ideal. I wanted to check the check as a black box.

0
source

Source: https://habr.com/ru/post/946218/


All Articles