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.
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 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.
unittest.PropertyMock can be used to mock descriptors in a way that doesn't require mocking internal implementation details:
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")
with mocker.patch('app.views.User.auth_token_set', new_callable=PropertyMock) as mock_auth_token_set:
mock_delete = mocker.MagicMock()
mock_auth_token_set.return_value.all.return_value.delete = mock_delete
response = ChangeEmail.as_view()(request)
assert response.status_code == status.HTTP_200_OK
assert mock_delete.call_count == 1
Related
I do coverage report and there are many place that not cover, do you have any idea how to I write test for th?
this views.py was not cover
def addreview(request, u_id, shop_id):
url = request.META.get('HTTP_REFERER')
shop = shop_detail.objects.get(id=shop_id)
user = Profile.objects.get(customer=u_id)
if request.method == 'POST':
form = ReviewForm(request.POST)
if form.is_valid():
data = Review()
data.review_text = form.cleaned_data['review_text']
data.review_rating = form.cleaned_data['review_rating']
data.shop = shop
data.user = user
data.save()
return redirect(url)
def rating(request):
if not request.user.is_authenticated:
return HttpResponseRedirect(reverse('customer_login'))
if request.method == 'POST':
form = RateUsForm(request.POST)
if form.is_valid():
rate = RateUs()
rate.rate_text = form.cleaned_data['rate_text']
rate.rating = form.cleaned_data['rating']
rate.user = request.user
rate.save()
return HttpResponseRedirect(reverse("index"))
return render(request, 'shop/rate.html')
models.py
class Review(models.Model):
user = models.ForeignKey(Profile,on_delete=models.CASCADE)
shop = models.ForeignKey(shop_detail,on_delete=models.CASCADE)
review_text = models.TextField(max_length=300)
review_rating = models.IntegerField()
def __str__(self):
return f"{self.review_rating}"
class RateUs(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
rate_text = models.TextField()
rating = models.IntegerField()
def __str__(self):
return f"{self.rating}"
forms.py
class ReviewForm(forms.ModelForm):
class Meta:
model= Review
fields= ["review_text", "review_rating"]
class RateUsForm(forms.ModelForm):
class Meta:
model= RateUs
fields= ["rate_text","rating"]
This is what I write for rating
def test_valid_rating(self):
rateus1 = RateUs.objects.first()
data={
"user": rateus1.user,
"rate_text": rateus1.rate_text,
"rating": rateus1.rating
}
response = self.client.post(reverse('rating'), data=data)
self.assertEqual(response.status_code, 302)
When I try to write test for addreview i got customer.models.Profile.DoesNotExist: Profile matching query does not exist.
Here is what you need to implement to get a way to test your function:
# Imports needed
import pytest
from unittest.mock import MagicMock, patch
def test_addreview(self):
# Here probably you will need to create a User before.
user = User.objects.create(username='testuser', id=15445)
shop = shop_detail.objects.create(id=19889)
Profile.objects.create(customer=user.pk)
mock_request = MagicMock(
POST={'review_text': 'test', 'review_rating': 5},
META={'HTTP_REFERER': 'http://testserver/'},
method='POST',
)
self.assert(Review.objects.count(), 0)
with patch('django.shortcuts.redirect') as redirect_mock:
addreview(mock_request, user.pk, shop.pk)
redirect_mock.assert_called_with('http://testserver/')
self.assert(Review.objects.count(), 1)
def test_addreview_not_redirected(self):
# Here probably you will need to create a User before.
user = User.objects.create(username='testuser', id=15445)
shop = shop_detail.objects.create(id=19889)
Profile.objects.create(customer=user.pk)
mock_request = MagicMock(
POST={'review_text': 'test', 'review_rating': 5},
META={'HTTP_REFERER': 'http://testserver/'},
method='GET',
)
self.assert(Review.objects.count(), 0)
with patch('django.shortcuts.redirect') as redirect_mock:
addreview(mock_request, user.pk, shop.pk)
redirect_mock.assert_not_called()
self.assert(Review.objects.count(), 0)
def test_addreview_raise_exception_profile_not_found(self):
# Here probably you will need to create a User before.
user = User.objects.create(username='testuser', id=15445)
shop = shop_detail.objects.create(id=19889)
mock_request = MagicMock(
POST={'review_text': 'test', 'review_rating': 5},
META={'HTTP_REFERER': 'http://testserver/'},
method='GET',
)
self.assert(Review.objects.count(), 0)
self.assert(Profile.objects.count(), 0)
with pytest.raises(Profile.DoesNotExist):
addreview(mock_request, user.pk, shop.pk)
self.assert(Review.objects.count(), 0)
self.assert(Profile.objects.count(), 0)
Probaly you will need to adapt a little bit each example as it is just a guide of how you have to implement it.
When working with this kind of test if you expect for example a Profile to exists you need to create it before to anticipate to the lines of code that will be executed once the function is called.
Mock are so important in this case we mock request and simulate all the properties that a request has.
We also mock redirect but in this case just to knwo if it was called or not.
At the end we generate a case where what we expect is the code to generate an exception when calling the funcion.
Normally to create model objects in test the best practice is to use https://factoryboy.readthedocs.io/en/stable/ take a look of that.
i just learning how to pytest my code in django, and i need your help. So, i have tested my forms.py already,and now i want to test my views.py. I know that i need to test is it post on page,like by response on by ORM, but i cant understand how to do that, probably with my factories or no?
This is my views.py
class AddPost(CreateView):
model = Posts
form_class = PostsForm
template_name = 'posts/addpost.html'
success_url = '/'
def form_valid(self, form):
instance = form.save(commit=False)
if self.request.user.is_authenticated:
instance.owner = self.request.user
instance.save()
return HttpResponseRedirect(self.get_success_url())
class ShowPost(ListView):
model = Posts
template_name = 'posts/allposts.html'
paginate_by = 2
this is test_forms
#pytest.mark.django_db(True)
class TestPostCreationForm:
def test_form(self):
proto_post = PostsFactory.build()
form_payload = {
'phone_number': proto_post.phone_number,
'title': proto_post.title,
'type': proto_post.type,
'text': proto_post.text,
'price': proto_post.price,
'status': proto_post.status,
'image': proto_post.image,
}
form = PostsForm(form_payload)
assert form.is_valid()
instance = form.save()
assert instance.phone_number == proto_post.phone_number
assert instance.title == proto_post.title
assert instance.price == proto_post.price
and factories
from users.tests.factories import UserFactory
def get_mock_img(name='test.png', ext='png', size=(50, 50), color=(256, 0, 0)):
file_obj = BytesIO()
image = Image.new("RGB", size=size, color=color)
image.save(file_obj, ext)
file_obj.seek(0)
return File(file_obj, name=name)
class PostsFactory(factory.DjangoModelFactory):
owner = SubFactory(UserFactory)
phone_number = factory.Faker("phone_number", locale='uk_UA')
title = factory.fuzzy.FuzzyText(length=50)
text = factory.fuzzy.FuzzyText(length=250)
price = factory.fuzzy.FuzzyDecimal(10.5, 50.5)
status = factory.fuzzy.FuzzyChoice(choices=['active', 'deactivated'])
type = factory.fuzzy.FuzzyChoice(choices=['private', 'business'])
image = get_mock_img()
class Meta:
model = 'posts.Posts'
Use the Django test client. Although I don't use pytest, so I can't be certain that it's completely separable from the Django test runner which I use. I'd be surprised if that was not the case
Typical usage (POST to a simple form, which is expected to succeed and redirect)
data = {
"number_per_gridbox": "10",
"submit": "Submit" }
url_fill_gridbox = reverse( "jobs:qa_break_into_gridboxes",
kwargs={'jobline': self.jobline.pk ,'stockline': self.stockline.pk})
response = self.client.post( url_gridbox, data)
self.assertRedirects( response, url_fill_gridbox)
where you are testing error responses or GET, you can obtain the rendered HTML with
body = response.content.decode()
and you can look at entities in the context data that was used for rendering:
form_errors = response.context['form'].errors
As for setting up some objects for the view to process, you can either just create and save them with code (often in the setUp method), or you can use faker and factory-boy
I have defined the following custome action for my ViewSet Agenda:
class AgendaViewSet(viewsets.ModelViewSet):
"""
A simple viewset to retrieve all the Agendas
"""
queryset = Agenda.objects.all()
serializer_class = AgendaSerializer
#action(detail=False, methods=['GET'])
def get_user_agenda(self, request, pk=None):
print('here1')
id = request.GET.get("id_user")
if not id:
return Response("No id in the request.", status=400)
id = int(id)
user = User.objects.filter(pk=id)
if not user:
return Response("No existant user with the given id.", status=400)
response = self.queryset.filter(UserRef__in=user)
if not response:
return Response("No existant Agenda.", status=400)
serializer = AgendaSerializer(response, many=True)
return Response(serializer.data)
Here, I'd like to unit-test my custom action named "get_user_agenda".
However, when I'm testing, the debug output("here1") doesn't show up, and it always returns 200 as a status_code.
Here's my test:
def test_GetUserAgenda(self):
request_url = f'Agenda/get_user_agenda/'
view = AgendaViewSet.as_view(actions={'get': 'retrieve'})
request = self.factory.get(request_url, {'id_user': 15})
response = view(request)
self.assertEqual(response.status_code, 400)
Note that:
self.factory = APIRequestFactory()
Am I missing something?
Sincerely,
You will have to use the method name of the custom action and not retrieve so:
view = AgendaViewSet.as_view(actions={'get': 'get_user_agenda'})
You have to specify request url
#action(detail=False, methods=['GET'], url_path='get_user_agenda')
def get_user_agenda(self, request, pk=None):
And in my opinion it would be better to use detail=True, and get pk from url.
For example: 'Agenda/pk_here/get_user_agenda/'
I have added a method to my viewset as follows:
class CustomImageViewSet(viewsets.ModelViewSet):
queryset = CustomImage.objects.all()
serializer_class = CustomImageSerializer
lookup_field = 'id'
#action(detail=True, methods=['get'], url_path='sepia/')
def sepia(self, request, id):
# do something
data = image_to_string(image)
return HttpResponse(data, content_type="image/png", status=status.HTTP_200_OK)
Since it is not a default or overridden request method, I am not sure how can I proceed writing a test for it. Any suggestions?
You're not clear on what the test should test but you can test the response status_code for example like this:
def test_sepia_api():
api_client = APIClient()
response = api_client.get(path="{path_to_your_api}/sepia/")
assert response.status_code == 200
I noticed you were using pytest. I'll assume you've got pytest-django too then (it really does make everything easier). I like using request factory since it's generally faster if you've got authentication needs.
def test_me(self, user, rf):
view = CustomImageViewSet()
request = rf.get("")
request.user = user # If you need authentication
view.request = request
response = view.sepia(request, 123)
assert response.data == BLAH
i am new to django!
I want to make a chatterbot chatbot in my website, for which i need to make a POST request in views. I have created a model. I am using mysql database for this.
I have visited github and other website and finally got a code, but it doesn't have the POST request
this is my models.py:
class Response(models.Model):
statement = models.ForeignKey(
'Statement',
related_name='in_response_to',
on_delete=False
)
response = models.ForeignKey(
'Statement',
related_name='+',
on_delete=False
)
unique_together = (('statement', 'response'),)
occurrence = models.PositiveIntegerField(default=0)
def __str__(self):
s = self.statement.text if len(self.statement.text) <= 20 else self.statement.text[:17] + '...'
s += ' => '
s += self.response.text if len(self.response.text) <= 40 else self.response.text[:37] + '...'
return s
this is where i need to make a POST request in views.py
def post(self, request, *args, **kwargs):
response = Response.objects.all()
if request.is_ajax():
input_data = json.loads(request.read().decode('utf-8'))
else:
input_data = json.loads(request.body.decode('utf-8'))
self.validate(input_data)
response_data = self.chatterbot.get_response(input_data)
return JsonResponse(response, response_data, status=200)
def get(self, request, *args, **kwargs):
data = {
'detail': 'You should make a POST request to this endpoint.',
'name': self.chatterbot.name,
'recent_statements': self._serialize_recent_statements()
}
# Return a method not allowed response
return JsonResponse(data, status=405)
If you're using django rest framework (DRF), i recommend you start by doing QuickStart and then Serialization steps. DRF has a really good documentation and in the Serialization you could find how to make a POST request by defining:
Models
Serializers
Api
Routers