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': '[email protected]'
}
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.
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()
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!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With