django test giving abnormal results - django

I am getting weird results when testing for 2 different views. They are both list views. The first one should only show active orders(active=True), the second one only showing historic views (completed=True).
I am using postgresql as a database also. Issue is that both the view works perfectly fine in the browser, however when testing pytest raises an error saying that an order that shouldn't be listed, is listed.
The views are as follows:
class OrderHistoryView(LoginRequiredMixin, ListView):
template_name = 'orders/order_history.html'
def get_queryset(self):
user = self.request.user
qs = Order.objects.filter(Q(buyer=user, completed=True)|Q(seller=user, completed=True))
return qs
class OrderListView(LoginRequiredMixin, ListView):
template_name = 'orders/order_list_view.html'
def get_queryset(self):
user = self.request.user
qs = Order.objects.filter(Q(buyer=user, active=True)|Q(seller=user, active=True))
return qs
The tests are:
class OrderHistoryViewTests(TestCase):
#classmethod
def setUp(self):
self.req = RequestFactory()
self.user = mixer.blend('user.CustomUser', email='test#test.com', password='1234')
self.user2 = mixer.blend('user.CustomUser')
self.advert = mixer.blend('advert.Advert', author=self.user)
self.offer = mixer.blend(
'offer.Offer', advert=self.advert, author=self.user2, receiver=self.user, accepted=True)
self.order = mixer.blend(
'orders.Order', advert=self.advert, seller=self.user2, buyer=self.user, offer=self.offer,
pending=True, completed=True, active=True, disputed=False, cancelled=False)
self.order2 = mixer.blend('orders.Order', completed=False, buyer=self.user)
def test_only_shows_completed(self):
request = self.req.get('/')
request.user = self.user
resp = OrderHistoryView.as_view()(request)
self.assertContains(resp, self.order)
self.assertNotContains(resp, order2)
The second test is exactly the same, it just tests that its only showing active orders
error message is :
FAILED orders/tests/test_views.py::OrderHistoryViewTests::test_only_shows_completed - AssertionError: 3 != 0 : Response should not contain '10'
FAILED orders/tests/test_views.py::OrderListViewTests::test_order_listing_showing_only_active - AssertionError: 2 != 0 : Response should not contain '15'
EDIT:
I have done some further investigation on this and have found that even that the first "self.assertContains(resp, self.order)" statement is failing also. However when i run test_views only this "self.assertContains(resp, self.order)" test passes, however when i run all the tests together it fails. I have noticed after commenting out 5 different views in the test_forms directory, when i run the tests they all pass. Issue is that there is nothing wrong with the 5 form tests, their is no mock statements on them and the tear down seems to be working.
I then put a print statement to see what queryset is actually showing up:
printing Order.objects.all() shows ", ]>" and when i print(self.order.completed) also comes up true. However the result of the test failing says : "AssertionError: False is not true : Couldn't find '25' in response"
Even though 25 is clearly there and completed. The view code looks perfect also and the view is a very simple view. What could be causing this?
Also i have noticed if i run py.test i only need to comment out 3 of the form tests and it will all pass, however with manage.py test i have to comment out 5 form tests for it to pass.
I just used print(resp.context_data) to see what is actually in the response...
{'paginator': None, 'page_obj': None, 'is_paginated': False, 'object_list': ]>, 'order_list': ]>, 'view': }
From here we can see the order "25" is actually in the response, i printed the username also and it matches.

Related

Django Tests: increase testsRun counter

In some Django Tests I have loop to test many things.
in the end-result it shows up as:
Ran 1 test in 3.456s
I would like to increment that counter for each loop, how can I do that?
It is using subTest() , but that does not update the counter (which I believe is a parameter testsRun)
my test looks something like this
class MyTestCase(TestCase):
def test_auth_pages(self):
pages = ['homepage', 'dashboard', 'profile']
for page in pages:
with self.subTest():
# ....testsRun += 1
self.c.login(username='test', password='test')
response = self.c.get(reverse_lazy(page))
self.assertEqual(200, response.status_code, msg=page)
self.c.logout()
response = self.c.get(reverse_lazy(page))
self.assertEqual(302, response.status_code, msg=page)
If you don't mind changing testing framework, consider pytest with pytest-django package. You can easily parametrize a test using #pytest.mark.parametrize:
import pytest
#pytest.mark.parametrize("page_name", ['homepage', 'dashboard', 'profile'])
def test_some_page(page_name, client):
client.login(username='test', password='test')
response = client.get(reverse_lazy(page))
assert response.status_code == 200
client.logout()
response = client.get(reverse_lazy(page))
assert response.status_code == 302
If not, you could create a test function factory that would accept the page name and return a test function for that page:
class MyTestCase(TestCase):
def _create_page_test(page_name):
def test_function(self):
self.c.login(username='test', password='test')
response = self.c.get(reverse_lazy(page_name))
self.assertEqual(200, response.status_code, msg=page_name)
self.c.logout()
response = self.c.get(reverse_lazy(page_name))
self.assertEqual(302, response.status_code, msg=page_name)
return test_function
test_homepage = _create_page_test("homepage")
test_dashboard = _create_page_test("dashboard")
test_profile = _create_page_test("profile")
The added benefit of such changes is that each page has a separate test, independent from the other. That makes debugging easier.
You can achieve this with a different test suite.
Check out test generators from the django-nose package
def test_evens():
for i in range(0, 5):
yield check_even, i, i*3 # this generates 5 different tests
def check_even(n, nn):
assert n % 2 == 0 or nn % 2 == 0

Test cases development for filter query search in Django

I am testing a Django Library application, which has a Book model and a search bar to filter those books that checks title__icontains = 'q'.
The url pattern:
path('search_book/', views.BookSearchListView.as_view(), name='search_book'),
The url routing:
http://127.0.0.1:8000/catalog/search_book/?q=house
Implementation of the following Class-based view:
class BookSearchListView(BookListView):
paginate_by = 3
def get_queryset(self):
result = super(BookSearchListView, self).get_queryset()
query = self.request.GET.get('q')
if query:
query_list = query.split()
result = result.filter(
reduce(operator.and_,
(Q(title__icontains=q) for q in query_list))
)
return result
In my tests.py, I have to develop test cases for the above view, but do not understand how to go about it. I have attempted the following:
class BookSearchListViewTest(TestCase):
"""
Test case for the Book Search List View
"""
def setUp(self):
test_user1 = User.objects.create_user(username='testuser1', password='1X<ISRUkw+tuK')
test_user1.save()
test_author = Author.objects.create(first_name='John', last_name='Smith')
Book.objects.create(title='House', author=test_author, summary='Published in 1990',
isbn='123456789123')
Book.objects.create(title='Money', author=test_author, summary='Published in 1991',
isbn='9876543210123')
Book.objects.create(title='Mouse', author=test_author, summary='Published in 1992',
isbn='1293874657832')
def test_redirect_if_not_logged_in(self):
response = self.client.get(reverse('books'))
self.assertRedirects(response, '/catalog/customer_login/?next=/catalog/books/')
def test_query_search_filter(self):
self.assertQuerysetEqual(Book.objects.filter(title__icontains='House'), ["<Book: House>"])
While the test_query_search_filter test runs successfully, in my coverage report, the class BookSearchListView is not getting tested.
I am a complete novice in Django and have just started out with test cases.
If you have parameter in your url then you should send it via url in your test case.
You created a Book object which title is House in your setUp method so;
def test_query_filter(self):
# If you have login required to access 'books' then
# you have to login with 'client.login' first.
url = '{url}?{filter}={value}'.format(
url=reverse('books'),
filter='q', value='Hou')
# With string format finally we expect a url like;
# '/books/q=Hou'
self.client.login(username='testuser1', password='1X<ISRUkw+tu')
response = self.client.get(url)
...
# test cases
...
You can test it like this:
def test_redirect_if_not_logged_in(self):
self.client.login(username='testuser1', password='1X<ISRUkw+tu')
response = self.client.get(reverse('books'))
self.assertQuerysetEqual(response.context['object_list'], Book.objects.all(), transform= lambda x:x)
You can check the testing tools documentation for more details.

Django test Client can only login once?

Our team is currently writing tests for our application. I am currently writing code to acces the views. These views are behind a login-screen, so our test first have to login and than peform the rest of the test. I've run into a very strange error. Basically My tests can only login once.
As you can see in the example below, both classes are doing the exact same thing, yet only one of them succeeds with the login, the other gives a '302 doest not equal 200' assertion error.
If I comment out the bottom one, the one at the top works, and vice versa.
Code that is testing different views also doesnt work, unless I comment out all other tests.
It doesnt matter if I login like shown below, or use a different variant (like self.client.login(username='test', password='password')).
Me and my team have no idea why Django is behaving this way and what we are doing wrong. Its almost as if the connection remains open and we would have to add code to close it. But the django-documentation doesnt mention any of this. DOes anyone know what we are doing wrong?
class FunctieListView_tests(TestCase):
"""Function listview only shows the data for the current_user / tenant"""
def setUp(self):
self.tenant = get_tenant()
self.function = get_function(self.tenant)
self.client = Client(HTTP_HOST='tc.tc:8000')
self.user = get_user(self.tenant)
def test_correct_function_context(self):
# Test if the view is only displaying the correct context data
self.client.post(settings.LOGIN_URL, {
'username': self.user.username,
'password': 'password'
}, HTTP_HOST='tc.tc:8000')
response = self.client.get(reverse('functie_list'))
self.assertEqual(response.status_code, 200)
self.assertTrue(response.context['functie_templates'] != None)
self.assertEqual(response.context['functie_templates'][0],
FunctieTemplate.objects.filter(linked_tenant=self.tenant)[0])
class FunctieListView_2_tests(TestCase):
"""Role Listview only shows the data for the current_user / tenant"""
def setUp(self):
self.tenant = get_tenant()
self.function = get_function(self.tenant)
self.client = Client(HTTP_HOST='tc.tc:8000')
self.user = get_user(self.tenant)
def test_correct_function_context_second(self):
#login
# Test if the view is only displaying the correct context data
self.client.post(settings.LOGIN_URL, {
'username': self.user.username,
'password': 'password'
}, HTTP_HOST='tc.tc:8000')
response = self.client.get(reverse('functie_list'))
self.assertEqual(response.status_code, 200)
self.assertTrue(response.context['functie_templates'] != None)
self.assertEqual(response.context['functie_templates'][0],
FunctieTemplate.objects.filter(linked_tenant=self.tenant)[0])
The users, tenants and functions are defined in a seperate utils file like so:
def get_user(tenant, name='test'):
u = User.objects.create_user(name, '{}#test.test'.format(name), 'password')
u.save()
u.profile.tenant = tenant
u.profile.tenant_role = generis.models.TENANT_OWNER
u.profile.save()
return u
def get_function(tenant):
userfunction = UserFunction.objects.create(name='test_functie', linked_tenant=tenant)
userfunction.save()
return userfunction
def get_tenant(slug_var='tc'):
f = elearning.models.FontStyle(font='foobar')
f.save()
c = elearning.models.ColorScheme(name='foobar', title='foo', text='fleeb', background='juice', block_background='schleem', box='plumbus')
c.save()
t = elearning.models.Tenant(name='tc', slug=slug_var, default_font_style=f, default_color_scheme=c)
t.save()
return t
My guess is that it happens because you are instantiating the Client yourself in setUp. Although it looks fine the outcome is obviously different from the regular behavior. I never had problems with login using the preinitialized self.client of django.test.TestCase.
Looking at django.test.client.Client, it says in the inline documentation:
Client objects are stateful - they will retain cookie (and thus session) details for the lifetime of the Client instance.
and a still existing cookie would explain the behavior you describe.
I cannot find HTTP_HOST in django.test.client.py, so I'm not sure whether you are really using that Client class at all. If you need access to a live server instance during tests, you could use Django's LiveServerTestCase.

How to count executed queries during selenium test?

I have tried this below mentioned code, but it didn't work for me.
class SeleniumTest(LiveServerTestCase):
#classmethod
def setUpClass(cls):
super().setUpClass()
cls.driver = PhantomJS()
#override_settings(DEBUG=True)
def test_queries(self)
with self.assertNumQueries(10):
self.driver.get(self.live_server_url + "/page-with-10-queries")
Output:
query['sql'] for query in self.captured_queries
AssertionError: 0 != 10 : 0 queries executed, 10 expected
Captured queries were:
To test the queries generated by a view, I use the following code:
def test_queries(self):
with self.assertNumQueries(3):
found = self.client.get(reverse('<url name>'))
print(found) # This is the line that initiates the Lazy query
The following is a not tested suggestion but if you insist on using selenium you may try:
#override_settings(DEBUG=True)
def test_queries(self)
with self.assertNumQueries(10):
found = self.driver.get(self.live_server_url + "/page-with-10-queries")
print(found) # Just an idea

Multiple ajax request (Singleton Pattern)

I am using django restless for an ajax POST request, which took almost 10 to 20 seconds.
Here is my code.
class testEndPoint(Endpoint):
def post(self, request):
testForm = TestEmailForm(request.data)
if testForm.is_valid():
sometable = EmailTable.object.get(**condition)
if sometable.is_email_sent == false:
#Send Email
#Took around 15 seconds
sometable.is_email_sent = true
sometable.save()
else:
result = testForm.errors
return serialize(result)
i am calling it via $.ajax, but the problem is if two request hit this url with milliseconds time difference, both request passed through if sometable.is_email_sent = false: condition.
How can i prevent multiple submission. Right now i have moved sometable.is_email_sent = true;sometable.save(); before email send part, but i need more generic solution as there are dozen more places where this is happening. I am on django 1.5
django restless
You should disable the originating input element before you start your ajax call (that will prevent the majority of these issues).
The remaining problems can be solved by using select_for_update
class testEndPoint(Endpoint):
#transaction.commit_manually
def post(self, request):
testForm = TestEmailForm(request.data)
if testForm.is_valid():
condition['is_email_sent'] = False
try:
rows = EmailTable.objects.select_for_update().filter(**condition)
for row in rows:
row.is_email_sent = True
row.save()
#Send Email
except:
transaction.rollback()
raise
else:
transaction.commit()
else:
result = testForm.errors
return serialize(result)
select_for_update will lock the rows until the end of the transaction (i.e. it needs to be inside a transaction). By adding is_email_sent=False to the condition, we can remove the if. I've moved the changing of is_email_sent above the "Send Email", but it is not strictly necessary -- in any case it will be undone by the transaction rolling back if there is an exception.