django-pytest - RequestFactory.get or client.get - python

thanks for your time.
i'd like to know when testing views is it better to create a request object using RequestFactory().get() or Client().get() or calling the view directly
RequestFactory():
from django.test import RequestFactory
from django.urls import reverse
def test_profile_view(self):
p1 = mixer.blend(Profile)
path = reverse('profile', kwargs={'pk': p1.id})
request = RequestFactory().get(path)
request.user = p1.user
response2 = views.profile_view(request, pk=p1.id)
assert response.status_code == 200
assert response.streaming == False
assert response2.charset == 'utf-8'
assert response2.status_code == 200
Client():
from django.test import Client
class TestView():
def test_profile_view(self):
p1 = mixer.blend(Profile)
path = reverse('profile', kwargs={'pk': p1.id})
response= Client().get(path)
response.user = p1.user
response2 = views.profile_view(request, pk=p1.id)
assert response.status_code == 200
assert response.streaming == False
assert response2.charset == 'utf-8'
assert response2.status_code == 200
and would like to understand the difference please

If you use the RequestFactory and call the view, then you avoid the middlewares. This has the benefit that less codes gets executed and your test is faster.
But it has the drawback, that things might be a bit different on the production site, since on prod the middlewares get called.
I use Django since several years, and I am unsure again and again what is better. It makes no big difference.
BTW: This looks strange: response.user = p1.user

Related

django pytest how to test a view with argument(id)

i have a question regarding using pytest. These are my very 1st tests. I have 2 views which
i want to test (simplest possible way).
Views:
class MenuView(View):
def get(self, request):
return render(request, 'diet_app/menu.html')
class CuisineDetailsView(View):
def get(self, request, id):
cuisine = Cuisine.objects.get(id=id)
recipes = cuisine.recipe_set.all()
return render(request, 'diet_app/cuisine_details.html', {'cuisine': cuisine, 'recipes': recipes})
Here are my tests:
def test_menu(client):
url = reverse('menu')
response = client.get(url)
assert response.status_code == 200
#pytest.mark.django_db
def test_cuisine_details_view(client):
url = reverse('cuisine-details')
response = client.get(url)
assert response.status_code == 200
Urls:
path('menu/', MenuView.as_view(), name='menu'),
path('cuisine_details/<int:id>/', CuisineDetailsView.as_view(), name='cuisine-details'),
1st test (menu view) is working properly
2nd test (cuisine details view) shows error
.NoReverseMatch: Reverse for 'cuisine-details' with no arguments not found. 1 pattern(s) tried: ['cuisine_details\\/(?P<id>
I know i should probably put somethere ID argument but tried few options and havent succeed. Will be grateful for any help/advise
You must pass the id as argument to the reverse function.
#pytest.mark.django_db
def test_cuisine_details_view(client):
url = reverse('cuisine-details', kwargs={'id': 123})
response = client.get(url)
assert response.status_code == 200

Setting Initial Values In a Dataclass Used in a Mock Before Every Test

My goal is to write unit tests for a REST API that interfaces with Flash Memory on a device. To do that, I need a way to mock the class that interfaces with Flash Memory.
I attempted to do that by using a Python Dataclass in the Mock, but I've discovered I do not have any way to set initial values prior to each test. As a result, each test case is getting values that are set by the previous test case. I need to fix that.
To test the API, I'm using the following code:
#dataclass
class FlashMemoryMock:
mac_address: str = 'ff:ff:ff:ff:ff:ff'
#pytest.fixture
def client(mocker):
mocker.patch('manufacturing_api.bsp.flash_memory.FlashMemory', new=FlashMemoryMock)
app = connexion.App(__name__, specification_dir='../openapi_server/openapi/')
app.app.json_encoder = JSONEncoder
app.add_api('openapi.yaml', pythonic_params=True)
app.app.config['TESTING'] = True
with app.app.test_client() as client:
yield client
def test_get_mac_address(client):
"""Test case for get_mac_address
Get the MAC Address
"""
headers = {
'Accept': 'application/json',
}
response = client.open(
'/mac_address',
method='GET',
headers=headers)
assert response.status_code == 200
assert response.is_json
assert response.json.get('status') == 'success'
assert response.json.get('mac_address') == 'ff:ff:ff:ff:ff:ff'
This test case will pass because the FlashMemoryMock Dataclass initializes mac_address to ff:ff:ff:ff:ff:ff Unfortunately it would fail if I run it after a test_put_mac_address test case if that test changes the mac_address value.
The flash memory controller code looks like this:
flash_memory = FlashMemoryWrapper()
def get_mac_address(): # noqa: E501
return flash_memory.get_mac_address()
The FlashMemoryWrapper class validates inputs (i.e. is the user trying to set a valid Mac Address) and includes the following code:
class FlashMemoryWrapper:
def __init__(self):
# Initialize the Flash controller
self.flash_memory = FlashMemory()
It's this FlashMemory class that I am trying to replace with a Mock. When I debug the test cases, I have verified FlashMemoryWrapper.flash_memory is referencing FlashMemoryMock. Unfortunately I no longer have any way to set initial values in the FlashMemoryMock Dataclass.
Is there a way to set initial values? Or should I set up the Mock a different way?
I think what you are looking for can be achieved with a bit of meta-programming in tandem with parametrization of fixtures.
def initializer(arg):
#dataclass
class FlashMemoryMock:
mac_address: str = arg
return FlashMemoryMock
#pytest.fixture
def client(mocker, request):
mocker.patch('manufacturing_api.bsp.flash_memory.FlashMemory', new=initializer(request.param))
app = connexion.App(__name__, specification_dir='../openapi_server/openapi/')
app.app.json_encoder = JSONEncoder
app.add_api('openapi.yaml', pythonic_params=True)
app.app.config['TESTING'] = True
with app.app.test_client() as client:
yield client
#pytest.mark.parametrize('client', ['ff:ff:ff:ff:ff:ff'], indirect=['client'])
def test_get_mac_address(client):
"""Test case for get_mac_address
Get the MAC Address
"""
headers = {
'Accept': 'application/json',
}
response = client.open(
'/mac_address',
method='GET',
headers=headers)
assert response.status_code == 200
assert response.is_json
assert response.json.get('status') == 'success'
assert response.json.get('mac_address') == 'ff:ff:ff:ff:ff:ff'
# some other test with a different value for mac address
#pytest.mark.parametrize('client', ['ab:cc:dd'], indirect=['client'])
def test_put_mac_address(client):
# some code here

python class method mocking failure

Trying to understand mocking/patching and I have a restful API project with three files (FYI, I'm using flask)
class1.py
domain.py
test_domain.py
class1.py file content:
class one:
def addition(self):
return 4+5
domain.py file content:
from class1 import one
class DomainClass(Resource):
def post(self):
test1 = one()
val = test1.addition()
return {'test' : val }
test_domain.py file content:
import my_app
from flask_api import status
from mock import patch
app = my_app.app.test_client()
def test_post():
with patch('domain.one') as mock:
instance = mock.return_value
instance.addition.return_value = 'yello'
url = '/domain'
response = app.post(url)
print response.data
assert status.HTTP_200_OK == response.status_code
assert mock.called
For my test_domain.py file, I've also tried this...
#patch('domain.one')
def test_post(mock_domain):
mock_domain.addition.return_value = 1
url = '/domain'
response = app.post(url)
print response.data
assert status.HTTP_200_OK == response.status_code
My assert for the status of 200 passes, however, the problem is that I'm not able to mock or patch the addition method to give me value of 1 in place of 9 (4+5). I also tried doing 'assert mock.called' and it failes as well. I know I should be mocking/patching where the 'one()' method is used, i.e. in domain.py not in class1.py. But I tried even mocking class1.one in place of domain.one and I still kept getting 9 and not 1. What am I doing wrong ?
******** Update
I've another dilemma on the same issue, I tried doing this in the test_domain file instead of patching....
from common.class1 import one
def test_post():
one.addition = MagicMock(return_value=40)
url = '/domain'
response = app.post(url)
print response.data
assert status.HTTP_200_OK == response.status_code
Question
In update above, I did not do a mock at the place where it is used (i.e.: domain.one.addition = MagicMock(...) and it still worked !!!! It seems it may be doing a global change. Why did this work ?
In the above example, 'one' is a class in the module class1.py. If I change this class 'one' to a function in class1.py, mocking does not work. It seems this function 'one' residing in module class1.py can not be mocked like this...one.return_value = 'xyz', why? Can it be mocked globally ?
There are some issues in your code. In the first example you forgot that patch() is applied in with context and the original code is recovered when the context end. Follow code should work:
def test_post():
with patch('domain.one') as mock:
instance = mock.return_value
instance.addition.return_value = 'yello'
url = '/domain'
response = app.post(url)
print response.data
assert status.HTTP_200_OK == response.status_code
assert mock.called
assert response.data['test'] == 'yello'
The second one have an other issue: if you want patch just addition method you should use:
#patch('domain.one.addition')
def test_post(mock_addition):
mock_addition.return_value = 1
...
assert mock_addition.called
assert response.data['test'] == 1
If you want mock all one class you should set the return value of addition method of mock instance returned by mock_domain call like in your first example:
#patch('domain.one')
def test_post(mock_domain):
mock_addition = mock_domain.return_value.addition
mock_addition.return_value = 1
...
assert mock_addition.called
assert response.data['test'] == 1

Django unit test. Simple example

I learn unit test with Django. How to write test for this function? I need this example to understand.
#login_required
def datas(request):
queryset = Data.objects.filter(user=request.user)
if queryset.count() == 0:
return redirect('/data/')
return render_to_response('data_list.html',
{'data': queryset},
context_instance=RequestContext(request))
#imports here
class YourTestCase(TestCase):
fixtures = ['user-data.json']
def setUp(self):
self.client = Client()
def test_empty_datas(self):
self.client.login(username='something', password='something')
response = self.client.get('/path/to/view/') # or reverse by name
self.assertEqual(response.status_code, 302,
'View did not redirect on empty queryset.')
def test_populated_datas(self):
self.client.login(username='something', password='something')
Data.objects.create(some_field=some_value)
response = self.client.get('/path/to/view/') # or reverse by name
self.assertEqual(response.status_code, 200,
'View did not return a 200.')
...and so on. user-data would need to contain at least one user, otherwise you won't be able to authenticate.

Django - view didn't return an HttpResponse object

I'm facing this exception error and I'm puzzled by it, as this method worked in similar system, appreciate any help or pointers. Many Thanks!
Exception Value: The view Project.qna.views.add_vote didn't return an HttpResponse object.
def add_vote(request):
if request.method == "POST":
q_id = request.POST['vote_form_q_id']
a_id = request.POST['vote_form_a_id']
vote_value = request.POST['vote_form_value']
ok = False
vote_num = None
name = None
if q_id:
try:
question = Question.objects.get(id=q_id)
question.num_vote += int(vote_value)
question.save()
vote_num = question.num_vote
name = 'Question_'+str(q_id)
ok = True
except Question.DoesNotExist:
pass
elif a_id:
try:
answer = Answer.objects.get(id=a_id)
answer.num_vote += int(vote_value)
answer.save()
vote_num = answer.num_vote
name = 'Answer_'+str(a_id)
ok = True
except Answer.DoesNotExist:
pass
if ok and request.is_ajax:
result = simplejson.dumps({
"vote_num": vote_num,
}, cls=LazyEncoder)
response = HttpResponse(result, mimetype='application/javascript')
response.set_cookie(name, datetime.now)
return response
Fix your indention please, also you seem to have a lot of workarounds that could be simplified.
Every django view should return a HttpResponse object, you seem to have a lot of places where this would not be the case. To narrow down your problem change every pass to a print statement to see where your code actually fails. It would be quite helpful if you could present your POST data.
Well it's hard to tell without seeing what kind of request you are making to the view. But are you sending a POST request? Because you don't handle GET requests in any way. Also the indentation is wrong. But that might just be cutting and pasting gone awry.
This is untested, but it's a cleaner and more robust design, which I believe fits in with your logic and highlights the points where returning an HttpResponse is necessary:
def add_vote(request):
if not (request.method == 'POST' and request.is_ajax):
return # Some suitable response here
try:
vote_value = int(request.POST.get('vote_form_value',''))
except ValueError as e:
pass # Some suitable response here
def saveobj(model, key, val): # helper function to reduce code repetition
item = model.objects.get(id=key)
item.num_vote += val
item.save()
return item.num_vote, '%s_%s' % (model.__class__.__name__, key)
for model, key in [(Question, 'vote_form_q_id'), (Answer, 'vote_form_a_id')]):
try:
new_vote_value, name = saveobj(model, request.POST[key], vote_value)
break
except (KeyError, ObjectDoesNotExist) as e:
continue # or error out
else:
pass # neither question or answer found - so suitable response here
# return ajax response here....

Categories