Testing Django Rest Framework POST returns 500 despite call working - django

Update
This issue was caused by me not including a token in the APIClient's header. This is resolved.
I have a standard ModelViewSet at /test-endpoint. I am trying to use APIClient to test the endpoint.
from rest_framework.test import APIClient
... # During this process, a file is uploaded to S3. Could this cause the issue? Again, no errors are thrown. I just get a 500.
self.client = APIClient()
...
sample_call = {
"name": "test_document",
"description": "test_document_description"
}
response = self.client.post('/test-endpoint', sample_call, format='json')
self.assertEqual(response.status_code, 201)
This call works with the parameters I set in sample_call. It returns a 201. When I run the test, however, I get a 500. How can I modify this to get the 201 passed?
I run the tests with python src/manage.py test modulename
To rule out the obvious, I copy-pasted the sample call into Postman and run it without issue. I believe the 500 status code is coming from the fact that I'm testing the call and not using it in a live environment.
No error messages are being thrown beyond the AssertionError:
AssertionError: 500 != 201
Full Output of testing
/home/bryant/.virtualenvs/REDACTED/lib/python3.4/site- packages/django_boto/s3/shortcuts.py:28: RemovedInDjango110Warning: Backwards compatibility for storage backends without support for the `max_length` argument in Storage.get_available_name() will be removed in Django 1.10.
s3.save(full_path, fl)
F
======================================================================
FAIL: test_create (sample.tests.SampleTestCase)
Test CREATE Document
----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/bryant/api/redacted/src/sample/tests.py", line 31, in test_create
self.assertEqual(response.status_code, 201)
AssertionError: 500 != 201
----------------------------------------------------------------------
Ran 1 test in 2.673s
FAILED (failures=1)
Destroying test database for alias 'default'...
The S3 warning is expected. Otherwise, all appears normal.

To debug a failing test case in Django/DRF:
Put import pdb; pdb.set_trace() just before the assertion and see the request.content as suggested by #Igonato, or you can just add a print(request.content), there is no shame for it.
Increase verbosity of your tests by adding -v 3
Use dot notation to investigate the specific test case: python src/manage.py test modulename.tests.<TestCase>.<function>
I hope these are useful to keep in mind.

Related

Pytest on Flask based API - test by calling the remote API

New to using Pytest on APIs. From my understanding, testing creates another instance of Flask. Additionally, from the tutorials I have seen, they also suggest to create a separate DB table instance to add, fetch and remove data for test purposes. However, I simply plan to use the remote api URL as host to simply make the call.
Now, I set my conftest like this, where the flag --testenv would indicate to make the get/post call on the host listed below:
import pytest
import subprocess
def pytest_addoption(parser):
"""Add option to pass --testenv=api_server to pytest cli command"""
parser.addoption(
"--testenv", action="store", default="exodemo", help="my option: type1 or type2"
)
#pytest.fixture(scope="module")
def testenv(request):
return request.config.getoption("--testenv")
#pytest.fixture(scope="module")
def testurl(testenv):
if testenv == 'api_server':
return 'http://api_url:5000/'
else:
return 'http://locahost:5000'
And my test file is written like this:
import pytest
from app import app
from flask import request
def test_nodes(app):
t_client = app.test_client()
truth = [
{
*body*
}
]
res = t_client.get('/topology/nodes')
print (res)
assert res.status_code == 200
assert truth == json.loads(res.get_data)
I run the code using this:
python3 -m pytest --testenv api_server
The thing I expect is that the test file would simply make a call to the remote api with the creds, fetch the data regardless of how it gets pulled in the remote code, and bring it here for assertion. However, I am getting the 400 BAD REQUEST error, with the error being like this:
assert 400 == 200
E + where 400 = <WrapperTestResponse streamed [400 BAD REQUEST]>.status_code
single_test.py:97: AssertionError
--------------------- Captured stdout call ----------------------
{"timestamp": "2022-07-28 22:11:14,032", "level": "ERROR", "func": "connect_to_mysql_db", "line": 23, "message": "Error connecting to the mysql database (2003, \"Can't connect to MySQL server on 'mysql' ([Errno -3] Temporary failure in name resolution)\")"}
<WrapperTestResponse streamed [400 BAD REQUEST]>
Does this mean that the test file is still trying to lookup the database locally for fetching? I am unable to figure out on which host are they sending the test url as well, so I am kind of stuck here. Looking to get some help around here.
Thanks.

Django AssertionError - 302 is not 302

I have a super simple unit test set up for an endpoint that accepts POST requests with a file, and upon successful upload redirects the user to a new page. The goal of this unit test it to ensure file uploads are working properly.
tests.py
c = Client()
with open('replays/static/test.txt', 'r', ) as f:
response = c.post(
'/upload/',
{
'summoner': 'test user',
'title': 'Testing title',
'replay': f
},
follow=False
)
print(response.status_code)
print(response.status_code == 302)
self.assertIs(response.status_code, 302)
Output
$ python manage.py test replays
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
302
True
======================================================================
FAIL: test_create_replay (replays.tests.ReplayCreationTest)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/path/to/project/tests.py", line 52, in test_create_replay
self.assertIs(response.status_code, 302)
AssertionError: 302 is not 302
----------------------------------------------------------------------
Ran 1 test in 0.173s
FAILED (failures=1)
Destroying test database for alias 'default'...
If I change the parameter for following redirects when calling the test client's post method, everything works as expected with a response_status of 200
tests.py - follow redirect
c = Client()
with open('replays/static/test.txt', 'r', ) as f:
response = c.post(
'/upload/',
{
'summoner': 'test user',
'title': 'Testing title',
'replay': f
},
follow=True
)
print(response.status_code)
print(response.status_code == 200)
self.assertIs(response.status_code, 200)
Output
$ python manage.py test replays
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
200
True
.
----------------------------------------------------------------------
Ran 1 test in 0.196s
OK
Destroying test database for alias 'default'...
What am I missing? It doesn't seem like this should be the expected behavior of the assertion statement. I am using Django 3.1.
AssertIs checks if x is y, in other words, that x and y refer to the same object. But you can have two int objects that are both 302, but not the same object.
You should use .AssertEqual(…) [Python-doc]:
self.assertEqual(302, response.status_code)
For small integers, the CPython interpreter will construct int objects for -5 to 256, and thus work with a flyweight pattern:
The current implementation keeps an array of integer objects for all integers between -5 and 256, when you create an int in that range you actually just get back a reference to the existing object.
This means that for int between -5 and 256, it will refer to the same object, for values outside that range, it often constructs a new object.

AttributeError: TestSwitch instance has no attribute 'assertTrue'

I have a following pyunit test case code where I am collecting the result of the function (True or False) and using it to drive my assertion. However, I am getting the "no attribute" error for assertTrue. What is missing here?
I am using python 2.7.8 and pyunit version of PyUnit-1.4.1-py2.7.
The same code when run from the Eclipse (pydev plugin) from my Mac, it works fine. Only when I take this to my Linux box, it does throw below error. So to me it looks like some package incompatibility problem.
import json
import unittest
class TestSwitch(unittest.TestCase):
def testFunction(self):
self.assertTrue(True, "test case failed")
Below is the test suite class.
import unittest
from mysample import TestSwitch
# Create an instance of each test case.
testCase = TestSwitch('testFunction')
# Add test cases to the test suite.
testSuite = unittest.TestSuite()
testSuite.addTest(testCase)
# Execute the test suite.
testRunner = unittest.TextTestRunner(verbosity=2)
testRunner.run(testSuite)
It throws below error.
bash-3.2$ python mysuite.py
testFunction (mysample.TestSwitch) ... ERROR
======================================================================
ERROR: testFunction (mysample.TestSwitch)
----------------------------------------------------------------------
Traceback (most recent call last):
File "workspace/pyunit/mysample.py", line 7, in testFunction
self.assertTrue(True, "test case failed")
AttributeError: TestSwitch instance has no attribute 'assertTrue'
----------------------------------------------------------------------
Ran 1 tests in 0.000s
FAILED (errors=1)
bash-3.2$
For now I've figured a workaround for this problem by using 'assertEqual' comparing with a boolean value and it works. I am not sure why 'assertTrue' and for that matter 'assertFalse' is having problem. I did not change any package version or anything.
The workaround code is as below.
17 def testFunction(self):
18 res = True
19 self.assertEqual(res, True, 'test case failed')

2 arguments missing but method works

Could you help me understand what is going on here. The question is about the error in the traceback. The failure is just as the illustration. And what I would like to illustrate that the function works.
Well, I was told that 2 positional arguments: 'view_instance' and 'address' are missing.
But the method really has taken those 2 positional arguments and worked happily till its logical end. In the interactive playing I show that I can catch the arguments transmitted.
Why does error appear? Thank you in advance for your help.
ADDED LATER:
Well, this seems to be because of the 'test_' beginning of the function.
Without "test" it works (def anonymous_user_redirected_to_login_page(self, view_instance, address):).
/photoarchive/general/tests.py
class GeneralTest(TestCase):
def test_anonymous_user_redirected_to_login_page(self, view_instance, address):
pdb.set_trace()
request = RequestFactory().get(address)
request.user = AnonymousUser()
response = view_instance(request)
self.assertEqual(response.status_code, 302)
self.assertEqual(response['location'], '/accounts/login/')
def test_anonymous_user_from_home_page_redirected_to_login_page(self):
view_instance = HomePageView.as_view()
address = '/'
self.test_anonymous_user_redirected_to_login_page(view_instance, address)
Traceback
(photoarchive) michael#michael:~/workspace/photoarchive/photoarchive$ python manage.py test general
Creating test database for alias 'default'...
FE
======================================================================
ERROR: test_anonymous_user_redirected_to_login_page (general.tests.GeneralTest)
----------------------------------------------------------------------
TypeError: test_anonymous_user_redirected_to_login_page() missing 2 required positional arguments: 'view_instance' and 'address'
======================================================================
FAIL: test_anonymous_user_from_home_page_redirected_to_login_page (general.tests.GeneralTest)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/michael/workspace/photoarchive/photoarchive/general/tests.py", line 29, in test_anonymous_user_from_home_page_redirected_to_login_page
self.test_anonymous_user_redirected_to_login_page(view_instance, address)
File "/home/michael/workspace/photoarchive/photoarchive/general/tests.py", line 23, in test_anonymous_user_redirected_to_login_page
self.assertEqual(response.status_code, 302)
AssertionError: 200 != 302
----------------------------------------------------------------------
Ran 2 tests in 0.002s
FAILED (failures=1, errors=1)
Destroying test database for alias 'default'...
Interactive playing:
(photoarchive) michael#michael:~/workspace/photoarchive/photoarchive$ python manage.py test general
Creating test database for alias 'default'...
> /home/michael/workspace/photoarchive/photoarchive/general/tests.py(20)test_anonymous_user_redirected_to_login_page()
-> request = RequestFactory().get(address)
(Pdb) view_instance
<function HomePageView at 0x7faa0f76fea0>
(Pdb) address
'/'
(Pdb)
test_anonymous_user_redirected_to_login_page() method is treated by unittest framework as a test method, because its name starts with test. The framework tries to execute it, but is not passing any arguments to it (test methods don't normally take any arguments). However, the method requires them, hence the error.
If this method is only a helper method to be called from the other method, name it so that it doesn't start with test, e.g. _test_anonymous_user_redirected_to_login_page().
Note that the traceback is not related to this problem. The traceback simply shows where the other test method failed at an assertion. That is, the other test method runs correctly (both in unittest run and in your interactive session).

Django HTTP Response object for GAE Cron

I am doing this using Django / GAE / Python environment:
cron:
#run events every 12 hours
and
def events(request):
# read all records
# Do some processing on a few records
return http.HTTPResponseGone('Some Records are modified' )
Result in production :
Job runs on time with 'failed' message
However, it has done the job exactly on the datastore as required
No error log entry seen
Dev : No errors ; returns the message 'Some Records are modified'
Is it possible to avoid HTTP Response returned ? There is no need for HTTPResponse for me, however, I have kept this as Dev server testing fails in its absence. Can some one
help me to make the code clean?
Gone is error 410. You should return 200 Success if the operation succeeds. When you return HttpResponse, the default status is 200.