Mocking a RelatedManager in Django 2

做~自己de王妃 提交于 2019-12-12 16:30:29

问题


This question is directly related to this question, but that one is now outdated it seems.

I am trying to test a view without having to access the database. To do that I need to Mock a RelatedManager on the user.

I am using pytest and pytest-mock.

models.py

# truncated for brevity, taken from django-rest-knox
class AuthToken(models.Model):
    user = models.ForeignKey(
        User, 
        null=False, 
        blank=False,
        related_name='auth_token_set', 
        on_delete=models.CASCADE
    )

views.py

class ChangeEmail(APIView):
    permission_classes = [permissions.IsAdmin]
    serializer_class = serializers.ChangeEmail

    def post(self, request, *args, **kwargs):
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)

        user = request.user
        user.email = request.validated_data['email']
        user.save()

        # Logout user from all devices
        user.auth_token_set.all().delete() # <--- How do I mock this?

        return Response(status=status.HTTP_200_OK)

test_views.py

def test_valid(mocker, user_factory):
    user = user_factory.build()
    user.id = 1

    data = {
        'email': 'foo@example.com'
    }

    factory = APIRequestFactory()
    request = factory.post('/', data=data)
    force_authenticate(request, user)

    mocker.patch.object(user, "save")

    related_manager = mocker.patch(
        'django.db.models.fields.related.ReverseManyToOneDescriptor.__set__',
        return_vaue=mocker.MagicMock()
    )
    related_manager.all = mocker.MagicMock()
    related_manager.all.delete = mocker.MagicMock()

    response = ChangeEmail.as_view()(request)
    assert response.status_code == status.HTTP_200_OK

Drawing from the answer in the linked question I tried to patch the ReverseManyToOneDescriptor. However, it does not appear to actually get mocked because the test is still trying to connect to the database when it tries to delete the user's auth_token_set.


回答1:


You'll need to mock the return value of the create_reverse_many_to_one_manager factory function. Example:

def test_valid(mocker):
    mgr = mocker.MagicMock()
    mocker.patch(
        'django.db.models.fields.related_descriptors.create_reverse_many_to_one_manager', 
        return_value=mgr
    )

    user = user_factory.build()
    user.id = 1
    ...
    mgr.assert_called()

Beware that the above example will mock the rev manager for all models. If you need a more fine-grained approach (e.g. patch User.auth_token's rev manager only, leave the rest unpatched), provide a custom factory impl, e.g.

def test_valid(mocker):
    mgr = mocker.MagicMock()
    factory_orig = related_descriptors.create_reverse_many_to_one_manager
    def my_factory(superclass, rel):
        if rel.model == User and rel.name == 'auth_token_set':
            return mgr
        else:
            return factory_orig(superclass, rel)

    mocker.patch(
        'django.db.models.fields.related_descriptors.create_reverse_many_to_one_manager',
        my_factory
    )

    user = user_factory.build()
    user.id = 1
    ...
    mgr.assert_called()



回答2:


If you use django's APITestCase, this becomes relatively simple.

class TestChangeEmail(APITestCase):
    def test_valid(self):
        user = UserFactory()
        auth_token = AuthToken.objects.create(user=user)

        response = self.client.post(
            reverse('your endpoint'), 
            data={'email': 'foo@example.com'}
        )

        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertFalse(AuthToken.objects.filter(user=user).exists())

This avoids mocking altogether and gives a more accurate representation of your logic.




回答3:


I accomplish this doing this(Django 1.11.5)

@patch("django.db.models.fields.related_descriptors.create_reverse_many_to_one_manager")
def test_reverse_mock_count(self, reverse_mock):
    instance = mommy.make(DjangoModel)

    manager_mock = MagicMock
    count_mock = MagicMock()
    manager_mock.count = count_mock()
    reverse_mock.return_value = manager_mock

    instance.related_manager.count()
    self.assertTrue(count_mock.called)

hope this help!



来源:https://stackoverflow.com/questions/55907594/mocking-a-relatedmanager-in-django-2

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