0

I have these models to test:

# models.py (simplified, name is a costum multilanguage field)
class NameType(models.Model):
    name = models.CharField(_('nome'), max_length=25, unique=True)

class NameLanguage(models.Model):
    name = models.CharField(_('nome'), max_length=25, unique=True)
    syntax = models.ManyToManyField(
        NameType, related_name='syntax_name',
        verbose_name=_('sintassi'))

To isolate the tests I want to use mock() (I already tested NameType)

# test_models.py
class NameLanguageTest(TestCase):
    def test_language_created(self):
        self.name = Mock(spec=NameType)
        self.surname = Mock(spec=NameType)
        self.romans = NameLanguage.objects.create(name='Romans')
        self.romans.syntax.add(self.name)
        self.romans.syntax.add(self.surname)
        self.assertEqual(NameLanguage.objects.all().count(), 1)
        self.assertEqual(
            list(NameLanguage.objects.get(name='romans').syntax.all()),
            [self.name, self.surname]
        )

but when I try to add self.name and self.surname to M2M syntax it gives me this error:

Traceback (most recent call last):
  File "E:\progetti\ElencoNomi\lists\tests\test_models.py", line 79, in test_language_created
    self.romans.syntax.add(self.name)
  File "e:\progetti\envs\ElencoNomi\lib\site-packages\django\db\models\fields\related_descriptors.py", line 938, in add
    through_defaults=through_defaults,
  File "e:\progetti\envs\ElencoNomi\lib\site-packages\django\db\models\fields\related_descriptors.py", line 1039, in _add_items
    if not router.allow_relation(obj, self.instance):
  File "e:\progetti\envs\ElencoNomi\lib\site-packages\django\db\utils.py", line
280, in allow_relation
    return obj1._state.db == obj2._state.db
  File "E:\Python\Python37\lib\unittest\mock.py", line 593, in __getattr__
    raise AttributeError("Mock object has no attribute %r" % name)
AttributeError: Mock object has no attribute '_state'

Also should I use self.name and self.surname (like I did in the code above) or just name and surname? There is difference?

Thank you

Edit: like advised in comment I add

    self.name._state = Mock()
    self.surname._state = Mock()

but it gives this error:

Traceback (most recent call last):
  File "E:\progetti\ElencoNomi\lists\tests\test_models.py", line 81, in test_language_created
    self.romans.syntax.add(self.name)
  File "e:\progetti\envs\ElencoNomi\lib\site-packages\django\db\models\fields\related_descriptors.py", line 938, in add
    through_defaults=through_defaults,
  File "e:\progetti\envs\ElencoNomi\lib\site-packages\django\db\models\fields\related_descriptors.py", line 1042, in _add_items
    (obj, self.instance._state.db, obj._state.db)
ValueError: Cannot add "<Mock spec='NameType' id='86024648'>": instance is on database "default", value is on database "<Mock name='mock._state.db' id='93279176'>"

Edit:

I'm surprised that I can't Mock a simple m2m field, anyway using factory_boy it works (maybe, please check):

# factories.py:
class NameTypeFactory(factory.DjangoModelFactory):
    class Meta:
        model = NameType

# test_models.py
class NameLanguageTest(TestCase):
    def test_language_created(self):
        self.name = NameTypeFactory()
        self.surname = NameTypeFactory()
        self.romans = NameLanguage.objects.create(
            name_en='Romans', name_it='Romani')
        self.romans.syntax.add(self.name)
        self.romans.syntax.add(self.surname)
        self.assertEqual(NameLanguage.objects.all().count(), 1)
        self.assertEqual(
            list(NameLanguage.objects.get(name_it='romani').syntax.all()),
            [self.name, self.surname]
        )

But is this doing really what I want?

fabio
  • 1,210
  • 2
  • 26
  • 55
  • Have you tried this? https://stackoverflow.com/a/18384610/2421682 I use model-bakery for mocks and have no experience with mock, so can't give this as an answer, but it sounded exactly like your issue. – Shane Nov 04 '19 at 22:20
  • Thank you, but it gives another error – fabio Nov 04 '19 at 22:37

1 Answers1

1

Django uses the models and tables to run the tests, there are more complex libraries that will comprehensively mock your data (and even create it in the DB if need be) such as factory_boy

you can do something like

import factory, factory.django
from . import models

class NameTypeFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = models.NameType 

class NameLanguageFactory(factory.django.DjangoModelFactory):
    @factory.post_generation
    def groups(self, create, extracted, **kwargs):
        if create and extracted:
            # A list of groups were passed in, use them
            for group in extracted:
                self.groups.add(group)
Dash Winterson
  • 1,197
  • 8
  • 19
  • This don't gives error but is it really testing my code? I'm asking because to me isn't so clear what's happening (I'm studying) – fabio Nov 05 '19 at 16:59
  • Yes, it's boilerplating the field values and using all of your code, you can verify this using a package like `coverage` – Dash Winterson Nov 08 '19 at 07:10