How to use Mock library to mock a Django ForeignKey value?

烈酒焚心 提交于 2021-02-07 12:34:41

问题


I have a model and I'm trying to test validation without invoking the database layer. Rather than describe with words I'll just post up some example code. The issue here is the ForeignKey relationship to Bar, which isn't relevant to what I'm trying to test but is stopping me from running the test that I want.

First, 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)

Lastly, 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?


回答1:


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

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 test your validation as a black box, I would pull the validation code into a separate method on your model which I would call from inside the clean() method. You can then unit test this validation code specifically. You will still need to do the _stage = Mock() assignment so you can create your instance of Foo, but at least you will be minimising the calls into Django.




回答2:


Well, for now I've simply switched my assert to self.assertRaises(models.BadFooError, foo.clean) (the difference being foo.clean in place of foo.full_clean). While this works, it doesn't seem ideal. I wanted to test the validation as a black box.



来源:https://stackoverflow.com/questions/16847332/how-to-use-mock-library-to-mock-a-django-foreignkey-value

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!