I am relatively new to Django and very new to writing unit tests. I'd like to ask for assistance but I'm a bit stuck with where to even begin. The app I'm working with allows a teacher to assign multiple assignments to a student. On the student dashboard, an assignment should only be available if the start date <= today's date. The student should only see the first assignment in the list.
I need to compose a unit test to cover this scenario:
manually assign multiple assignments to a student
use the same query that is used for the student dashboard to check that the only assignments returned are the ones with a
start date <= today's date
check that the student only sees the first assignment (with the earliest start date) in the list.
Below I have posted the relevant code that is pulling what displays on the student dashboard. Please let me know if additional code is needed to help me get started with this. Thanks very much for any help you can offer!
Edit: I would like to only use the built in django.test features for now, if possible
from my home/views.py file
#login_required
def index(request):
user_type = request.user.type.text
if user_type == 'Student':
""" Only return the first test so the student sees one test at a time"""
assignment = Assignment.objects.filter(
student=request.user,
start_date__lte=datetime.date.today(),
completed=False).first()
if (assignment):
context = {
'test_pk': assignment.test.pk,
}
else:
context = {}
return render(request, 'home/student.html', context)
Basics of testing stuff like this goes roughly like:
Create the desired data manually
Create the action/conditions that are happening in the view(maybe send a request to the view)
Check the result with the previously manually created data.
So, start with creating some Assignment objects for students.
Run your view(send a request to your view logged in as the previously created user)
Check if the desired outcome exist in the returned html.
I would suggest you to use pytest and factoryboy for that, there's a lot of great tutorials online to use it with Django.
For you example it would be something like this
You need first to init the session, we can create fixture for that
import pytest
import factory
#pytest.fixture
def client():
from django.test.client import Client
return Client(HTTP_USER_AGENT='pytest')
then we should init the session, another fixture:
#pytest.fixture
def session(client):
# your custom session her
user = #use factory for the user
client.user = user
# your defaults for
# client.GET
# client.POST
# client.META
return client
class AssignmentFactory(factory.django.DjangoModelFactory):
class Meta:
model = Assignment
django_get_or_create = ('any attribute you like',)
# default the attributes you want to create here
# For example
name = "assignment one"
Then the test can be something like this
#pytest.mark.django_db
def test_retrieve_assignment_success(session):
path = reverse("view_name")
assignment = AssignmentFactory()
res = session.get(path=path, data={}, follow=False)
json_res = res.json()
assert json_res.get('context') is not None
assert assigment.pk == json_res.get('context').get('test_pk')
#pytest.mark.django_db
def test_retrieve_assignment_fail(session):
path = reverse("view_name")
res = session.get(path=path, data={}, follow=False)
json_res = res.json()
assert json_res.get('context') is not None
assert json_res.get('context') == {}
Related
For the life of me I cannot figure this out and I'm having trouble finding information on it.
I have a Django view which accepts an argument which is a primary key (e.g: URL/problem/12) and loads a page with information from the argument's model.
I want to mock models used by my view for testing but I cannot figure it out, this is what I've tried:
#patch('apps.problem.models.Problem',)
def test_search_response(self, problem, chgbk, dispute):
problem(problem_id=854, vendor_num=100, chgbk=122)
request = self.factory.get(reverse('dispute_landing:search'))
request.user = self.user
request.usertype = self.usertype
response = search(request, problem_num=12)
self.assertTemplateUsed('individual_chargeback_view.html')
However - I can never get the test to actually find the problem number, it's as if the model does not exist.
I think that's because if you mock the entire model itself, the model won't exist because any of the functions to create/save it will have been mocked. If Problem is just a mock model class that hasn't been modified in any way, it knows nothing about interacting with the database, the ORM, or anything that could be discoverable from within your search() method.
One approach you could take rather than mocking models themselves would be to create FactoryBoy model factories. Since the test database is destroyed with each test run, these factories are a great way to create test data:
http://factoryboy.readthedocs.io/en/latest/
You could spin up a ProblemFactory like so:
class ProblemFactory(factory.Factory):
class Meta:
model = Problem
problem_id = factory.Faker("pyint")
vendor_num = factory.Faker("pyint")
chgbk = factory.Faker("pyint")
Then use it to create a model that actually exists in your database:
def test_search_response(self, problem, chgbk, dispute):
problem = ProblemFactory(problem_id=854, vendor_num=100, chgbk=122)
request = self.factory.get(reverse('dispute_landing:search', kwargs={'problem_id':problem.id}))
request.user = self.user
request.usertype = self.usertype
response = search(request, problem_num=854)
self.assertTemplateUsed('individual_chargeback_view.html')
I want to write a unittest for this method in Django.
def activate(request):
id = int(request.GET.get('id'))
user = User.objects.get(id=id)
user.is_active = True
user.save()
return render(request, 'user_authentication/activation.html')
I wrote sth like this:
def test_activate_view(self):
response = self.client.get('/activation', follow=True)
self.assertTemplateUsed(response, 'user_authentication/activation.html')
It doesn't work because I get an error:
id = int(request.GET.get('id'))
TypeError: int() argument must be a string or a number, not 'NoneType':
What should I change in my test ?
Your view reads data from request.GET - you need to pass this data:
response = self.client.get('/activation?id=1', follow=True)
You also fetch this user afterwards from your database. Therefor you need to load some fixture data. Create a fixture using manage.py dumpdata and load it in your unit test like that:
class UserTestCase(TestCase):
fixtures = ['fixture.json', 'users']
Read the docs about loading fixtures for detailed explanations.
Note regarding your approach
You should not use the users id for this use case. One can easily guess this id and activate accounts.
Someone might register once with a valid email address, receive your link with the id inside and can afterwards create a bunch of accounts without providing a valid email address.
Instead you might generate a unique and random secret (aka token) and associate this token with the user. Your view should accept those tokens and resolve the user based on it. This way one can no longer easily activate.
This is my code:
Link to my imports are here:
https://github.com/django/django/blob/master/django/core/urlresolvers.py
https://github.com/django/django/blob/master/django/contrib/auth/models.py
https://github.com/tomchristie/django-rest-framework/blob/master/rest_framework/status.py
https://github.com/tomchristie/django-rest-framework/blob/master/rest_framework/test.py
from django.core.urlresolvers import reverse
from django.contrib.auth.models import User
from rest_framework import status
from rest_framework.test import APITestCase
class UserTests(APITestCase):
def test_create_user(self):
"""
Ensure we can create a new user object.
"""
url = reverse('user-list')
data = {'username': 'a', 'password': 'a', 'email': 'a#hotmail.com'}
# Post the data to the URL to create the object
response = self.client.post(url, data, format='json')
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
# Check the database to see if the object is created.
# This check works.
self.assertEqual(User.objects.count(), 1)
def test_get_user(self):
"""
Ensure we can get a list of user objects.
"""
# This fails and returns an error
self.assertEqual(User.objects.count(), 1)
When I run the test, it raises an error saying AssertionError: 0 != 1 because in the function test_get_user, the user created in test_create_user is not visible. Is there a way for me to get all the methods in a class to share the same database so that if I create a user in test_create_user, I can access it in the methods which come below it?
Edit: The reason I want them to share the same database for all the methods is because all of my test cases in the UserTests class will need a user created so I don't want to have to repeat the same code all the time even when it is tested in test_create_user.
I know I can use def setUp(self) but I'm doing a "create user" test in my first method so I want to be able to test if I can create it first before creating it in def setUp(self).
You should set up your data explicitly in each test. Tests must not depend on each other.
A lot of times, I wrote queries like the following:
pony.orm.select(u for u in User if u.available and u.friends > 0 and ...)
So, I would like to write my own version of select, an alternative of it. Something like to avoid me writing every time the first part of the predicate, if u.available and u.friends > 0.
My question is more general: how can I write a function like select that it accepts arguments like those that select method or count method can accept.
Let's define a User entity in the following way:
from datetime import datetime, timedelta
from pony.orm import *
db = Database('sqlite', ':memory:')
class User(db.Entity):
username = Required(str, unique=True)
password = Required(str)
friends = Set("User", reverse='friends') # many-to-many symmetric relation
online = Required(bool, default=False) # if user is currently online
last_visit = Optional(datetime) # last visit time
disabled = Required(bool, default=False) # if user is disabled by administrator
sql_debug(True)
db.generate_mapping(create_tables=True)
Now we can define some convenient functions to retrieve most frequently used types of users. The first function will return the users who are not disabled by the admin:
def active_users():
return User.select(lambda user: not user.disabled)
In that function I use select method of User entity which accepts a lambda function, but the same function can be written using global select function which accepts a generator expression:
def active_users():
return select(u for u in User if not user.disabled)
The result of active_users function is a query object. You can call filter method of the query object to produce more specific query. For example, I can use active_users function to select active users whose names start with the 'A' letter:
users = active_users().filter(lambda user: user.name.startswith('A')) \
.order_by(User.name)[:10]
Now I want to find users who visit the site in a few last days. I can define another function which uses the query returned from the previous function and augment it in the following way:
def recent_users(days=1):
return active_users().filter(lambda u: u.last_visit > datetime.now() - timedelta(days))
In this example I pass the days argument to the function and use its value inside the filter.
You can define a set of such functions which will form data access layer of your application. Some more examples:
def users_with_at_least_n_friends(n=1):
return active_users().filter(lambda u: count(u.friends) >= n)
def online_users():
return User.select(lambda u: u.online)
def online_users_with_at_least_n_online_friends(n=1):
return online_users().filter(lambda u: count(f for f in u.friends if f.online) >= n)
users = online_users_with_at_least_n_online_friends(n=10) \
.order_by(User.name)[:10]
In the examples above I define global functions. Another option is to define these functions as a classmethods of the User entity:
class User(db.Entity):
username = Required(str, unique=True)
...
#classmethod
def name_starts_with(cls, prefix):
return cls.select(lambda user: not user.disabled
and user.name.startswith(prefix))
...
users = User.name_starts_with('A').order_by(desc(User.last_visit))[:10]
If you might want to have a generic function which can be applied to different entity classes, then you need to pass the entity class as a parameter. For example, if a number of different classes have the deleted attribute, and you want to have a generic method to select only non-deleted objects, you can write something like that:
def select_active(cls):
return cls.select(lambda obj: not obj.deleted)
select_active(Message).filter(lambda msg: msg.author == current_user)
All functions provided above have one drawback - they are not composable. You cannot get a query from one function and augment it with another function. If you want to have a function which can augment existing query, that function should accept the query as an argument. Example:
def active_users():
return User.select(lambda user: not user.disabled)
def name_starts_with(query, prefix):
return query.filter(lambda user: user.name.startswith('prefix'))
The name_starts_with function can be applied to another query:
users1 = name_starts_with(active_users(), 'A').order_by(User.last_visited)
users2 = name_starts_with(recent_users(), 'B').filter(lambda user: user.online)
Also we are working on the query extension API which will allow a programmer to write custom query methods. When we release this API it will be possible to just chain custom query methods together in the following way:
select(u for u in User).recent(days=3).name_starts_with('A')[:10]
Hope I answered your question. If this is the case, please accept the answer as a correct one.
I have a weired problem with couple of queries I am trying to run.
I have built a method which returns a tuple of result from the query-
def get_activeproducts():
query = Product.gql("WHERE active = True")
choices = []
for obj in query:
choices.append((str(obj.key()), obj.name))
return choices
The problem is, the result is same for each call. Even if products are deleted or changed to 'False' in the product attribute 'active'. The result will be refreshed only when I restart the sdk server. In production, it just doesnt change till I change versions.
I have seen similar issue with one more query where the query property is BooleanProperty.
Any idea on how this could be fixed?
EDIT:
I am using the method in a tipfy application. It is used to populate a select field in wtforms. 'choices' basically takes in a list of tuples (value, name) pair.
class InvoiceForm(Form):
product = SelectField('Product', choices=get_activeproducts())
I dont have any issue with editing. WHen I check it from the admin end, I can see that certain products are set to 'False'. And even if I empty(delete) the whole list of products, I get the same list I got the first time.
I am not using caching anywhere in the application.
Your class definition is getting cached by the App Engine runtime when an instance is started, with the default set to what it was when the instance started. To make the choices dynamic, you need to set them at runtime.
Example from the wtforms (which IIRC is what tipfy is using) docs; will need to be adjusted for App Engine queries:
class UserDetails(Form):
group_id = SelectField(u'Group', coerce=int)
def edit_user(request, id):
user = User.query.get(id)
form = UserDetails(request.POST, obj=user)
form.group_id.choices = [(g.id, g.name) for g in Group.query.order_by('name')]
when you create your form, the function is called once.
you can overload the form __init__.py function to do this cleanly
class InvoiceForm(Form):
product = SelectField(u'Group', choices=[])
def __init__(self, product_select, *args, **kwargs)
super(InvoiceForm, self).__init__(*args, **kwargs)
self.product.choices = select_dict
----
form = InvoiceForm(product_select=get_activeproducts())