在Django 2中模拟RelatedManager

时间:2019-04-29 16:41:14

标签: django django-rest-framework pytest python-mock pytest-django

这个问题与this question直接相关,但是现在看来已经过时了。

我试图测试一个视图而不必访问数据库。为此,我需要在用户上模拟RelatedManager

我正在使用pytestpytest-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

从链接问题的答案中,我尝试修补ReverseManyToOneDescriptor。但是,它似乎并未真正受到嘲笑,因为该测试在尝试删除用户的auth_token_set时仍在尝试连接数据库。

3 个答案:

答案 0 :(得分:1)

如果使用Django的APITestCase,这将变得相对简单。

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())

这完全避免了模拟,并提供了更准确的逻辑表示。

答案 1 :(得分:1)

您需要模拟create_reverse_many_to_one_manager工厂函数的返回值。示例:

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()

请注意,上面的示例将模拟所有模型的转速管理器。如果您需要更细粒度的方法(例如,仅修补程序User.auth_token的转速管理器,其余的则不进行修补),请提供自定义的工厂实现,例如

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 :(得分:0)

我做到这一点(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)

希望获得帮助!