I have a simple form for registering new user. I wrote a test case for it. It looks as follows:
class AccountTestCase(LiveServerTestCase):
def setUp(self):
self.selenium = webdriver.Firefox()
super(AccountTestCase, self).setUp()
def tearDown(self):
self.selenium.quit()
super(AccountTestCase, self).tearDown()
def test_register(self):
selenium = self.selenium
#Opening the link we want to test
selenium.get('http://localhost:8000/register/')
#find the form element
first_name = selenium.find_element_by_id('id_first_name')
last_name = selenium.find_element_by_id('id_last_name')
username = selenium.find_element_by_id('id_username')
email = selenium.find_element_by_id('id_email')
password1 = selenium.find_element_by_id('id_password1')
password2 = selenium.find_element_by_id('id_password2')
submit = selenium.find_element_by_id('btn_signup')
#Fill the form with data
first_name.send_keys('abc')
last_name.send_keys('abc')
username.send_keys('abc')
email.send_keys('abc#gmail.com')
password1.send_keys('abcabcabc')
password2.send_keys('abcabcabc')
#submitting the form
submit.send_keys(Keys.RETURN)
#check the returned result
self.assertTrue('Success!' in selenium.page_source)
When I ran the test case for the first time it passed with flying colors, but on the second run it failed.
After little investigation I realized a user with credentials from test cases is already created in my database. Thus, when I ran test case for second time it failed to create a new user, as user with these details is already present. (I can see user from Django admin).
I believe this is not the expected behaviour of LiveServerTestCase(or any type of test case). In setup, a temporary database is created, test case is ran on it, and destroyed in tearDown phase.
I want to know if, this is the intended behavior? if not why this is happening ? How can I avoid doing it ?
I have not made any changes in settings.py which are related to selenium or testing (is there a flag or something that needs to be set?). Also, I need to keep the server running for this to work (is this normal?) .
As pointed by #Paulo Almeida :
I was using a wrong url. The URL that I should be using is self.live_server_url . Since I was using http://localhost:8000/register/ It was expecting server to be running and creating records there.
Thanks.
Related
I have a Django application that executes a full-text-search on a database. The view that executes this query is my search_view (I'm ommiting some parts for the sake of simplicity). It just retrieve the results of the search on my Post model and send to the template:
def search_view(request):
posts = m.Post.objects.all()
query = request.GET.get('q')
search_query = SearchQuery(query, config='english')
qs = Post.objects.annotate(
rank=SearchRank(F('vector_column'), search_query) + TrigramSimilarity('post_title', query)
).filter(rank__gte=0.15).order_by('-rank'), 15
)
context = {
results = qs
}
return render(request, 'core/search.html', context)
The application is working just fine. The problem is with a test I created. Here is my tests.py:
class SearchViewTests(TestCase):
def test_search_without_results(self):
"""
If the user's query did not retrieve anything
show him a message informing that
"""
response = self.client.get(reverse('core:search') + '?q=eksjeispowjskdjies')
self.assertEqual(response.status_code, 200)
self.assertContains(response.content, "We didn\'t find anything on our database. We\'re sorry")
This test raises an ProgrammingError exception:
django.db.utils.ProgrammingError: function similarity(character varying, unknown) does not exist
LINE 1: ...plainto_tsquery('english'::regconfig, 'eksjeispowjskdjies')) + SIMILARITY...
^
HINT: No function matches the given name and argument types. You might need to add explicit type casts.
I understand very well this exception, 'cause I got it sometimes. The SIMILARITY function in Postgres accepts two arguments, and both need to be of type TEXT. The exception is raising because the second argument (my query term) is of type UNKNOWN, therefore the function won't work and Django raises the exception. And I don't understand why, because the actual search is working! Even in the shell it works perfectly:
In [1]: from django.test import Client
In [2]: c = Client()
In [3]: response = c.get(reverse('core:search') + '?page=1&q=eksjeispowjskdjies')
In [4]: response
Out[4]: <HttpResponse status_code=200, "text/html; charset=utf-8">
Any ideas about why test doesn't work, but the actual execution of the app works and console test works too?
I had the same problem and this how I solved it in my case:
First of all, the problem was that when Django creates the test database that it is going to use for tests it does not actually run all of your migrations. It simply creates the tables based on your models.
This means that migrations that create some extension in your database, like pg_trgm do not run when creating the test database.
One way to overcome this is to use a fixture in your conftest.py file which will make create said extensions before any tests run.
So, in your conftest.py file add the following:
# the following fixture is used to add the pg_trgm extension to the test database
#pytest.fixture(scope="session", autouse=True)
def django_db_setup(django_db_setup, django_db_blocker):
"""Test session DB setup."""
with django_db_blocker.unblock():
with connection.cursor() as cursor:
cursor.execute("CREATE EXTENSION IF NOT EXISTS pg_trgm;")
You can of course replace pg_trgm with any other extension you require.
PS: You must make sure the extension you are trying to use works for the test database you have chosen. In order to change the database used by Django you can change the value of
DATABASES = {'default': env.db('your_database_connection_uri')}
in your application's settings.py.
A few of my tests are failing repeatedly so I'm using the shortest one as an example for this question
test
def test_delete_char(self):
with self.client:
start = characters.query.count() #num of entries in db
self.client.post(
url_for("delete", identity=1),
follow_redirects=True)
end = characters.query.count() #num of entries in db after deletion
self.assertTrue(start > end) #check if delete has gone through
route
#app.route("/delete/<identity>", methods=["GET", "POST"])
def delete(identity):
form = DeleteForm()
if form.validate_on_submit():
char = characters.query.filter_by(id=identity).first()
db.session.delete(char)
db.session.commit()
return redirect(url_for("home"))
return render_template("delete.html", form=form, identity=identity, title="Delete")
When running this function on the website manually there are no issues. Using pytest I can see that it doesn't get past if form.validate_on_submit(): in the delete function. I can only conclude that my self.client.post is wrong but I have no idea what the issue is. Any help would be appreciated
I found the issue. In my setUp function I had set WTF_CSRF_ENABLE=False instead of WTF_CSRF_ENABLED=False. this meant it was looking for a CSRF token that wasn't provided whenever a wtforms containing function was tested. Moral of the story is don't use vim/an editor without error highlighting
here's my view (simplified):
#login_required(login_url='/try_again')
def change_bar(request):
foo_id = request.POST['fid']
bar_id = request.POST['bid']
foo = models.Foo.objects.get(id=foo_id)
if foo.value > 42:
bar = models.Bar.objects.get(id=bar_id)
bar.value = foo.value
bar.save()
return other_view(request)
Now I'd like to check if this view works properly (in this simplified model, if Bar instance changes value when it should). How do I go about it?
I'm going to assume you mean automated testing rather than just checking that the post request seems to work. If you do mean the latter, just check by executing the request and checking the values of the relevant Foo and Bar in a shell or in the admin.
The best way to go about sending POST requests is using a Client. Assuming the name of the view is my_view:
from django.test import Client
from django.urls import reverse
c = Client()
c.post(reverse('my_view'), data={'fid':43, 'bid':20})
But you still need some initial data in the database, and you need to check if the changes you expected to be made got made. This is where you could use a TestCase:
from django.test import TestCase, Client
from django.urls import reverse
FooBarTestCase(TestCase):
def setUp(self):
# create some foo and bar data, using foo.objects.create etc
# this will be run in between each test - the database is rolled back in between tests
def test_bar_not_changed(self):
# write a post request which you expect not to change the value
# of a bar instance, then check that the values didn't change
self.assertEqual(bar.value, old_bar.value)
def test_bar_changes(self):
# write a post request which you expect to change the value of
# a bar instance, then assert that it changed as expected
self.assertEqual(foo.value, bar.value)
A library which I find useful for making setting up some data to execute the tests easier is FactoryBoy. It reduces the boilerplate when it comes to creating new instances of Foo or Bar for testing purposes. Another option is to write fixtures, but I find that less flexible if your models change.
I'd also recommend this book if you want to know more about testing in python. It's django-oriented, but the principles apply to other frameworks and contexts.
edit: added advice about factoryboy and link to book
you can try putting "print" statements in between the code and see if the correct value is saved. Also for update instead of querying with "get" and then saving it (bar.save()) you can use "filter" and "update" method.
#login_required(login_url='/try_again')
def change_bar(request):
foo_id = request.POST['fid']
bar_id = request.POST['bid']
foo = models.Foo.objects.get(id=foo_id)
if foo.value > 42:
models.Bar.objects.filter(id=bar_id).update(value=foo.value)
#bar.value = foo.value
#bar.save()
return other_view(request)
I have a django rest framework test, it is just a wrapper over regular django tests that works exactly the same way. The code looks like this:
user_created = User.objects.create_user(first_name="Wally", username="farseer#gmail.com", password="1234",
email="farseer#gmail.com")
client_created = Client.objects.create(user=user_created, cart=cart)
data_client_profile["user"]["first_name"] = "Apoc"
response = self.client.put(reverse("misuper:client_profile"), data_client_profile, format="json")
client_created.refresh_from_db() # Tried this too
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(client_created.user.first_name, data_client_profile["user"]["first_name"])
So, I want to update the client_created object with some data in the dict data_client_profile, then assertEqual that the client.user.first_name is "Apoc".
Here is the code in the view, I added two pdb.set_trace() that will help more than just pasting all the code:
pdb.set_trace()
client_existing_user_obj.phone = phone
client_existing_user_obj.user.email = email
client_existing_user_obj.user.first_name = first_name # Updating here!
client_existing_user_obj.user.last_name = last_name
client_existing_user_obj.user.save()
client_existing_user_obj.save()
pdb.set_trace()
The first pdb break shows this:
(Pdb) client_existing_user_obj.user.username
u'farseer#gmail.com' # Make sure I'm updating the created object
(Pdb) client_existing_user_obj.user.first_name
u'Wally' # First name is not updated yet
The second pdb break shows this:
(Pdb) client_existing_user_obj.user.first_name
u'Apoc' # Looks like the first name has being updated!
But, when the test runs I get the error:
self.assertEqual(client_created.user.first_name, data_client_profile["user"]["first_name"])
AssertionError: 'Wally' != 'Apoc'
Why does it fail? I even call refresh_from_db(). I confirm it has being updated in the view, but then in the test it looks like it has not. I don't understand.
Note that the docs for refresh_from_db say that client_created.user will not be refreshed by client_created.refresh_from_db(), because client_created.user_id has stayed the same:
The previously loaded related instances for which the relation’s value is no longer valid are removed from the reloaded instance. For example, if you have a foreign key from the reloaded instance to another model with name Author, then if obj.author_id != obj.author.id, obj.author will be thrown away, and when next accessed it will be reloaded with the value of obj.author_id.
Therefore you need to refresh client_created.user:
client_created.user.refresh_from_db()
or refetch client_created yourself:
client_created = Client.objects.get(pk=client_created.pk)
It's the user you need to refresh from the database, since that's the object you're modifying:
user_created.refresh_from_db()
I'm finally setting up testing for my Django app, but I'm having difficulties getting started. I'm using model_mommy to create dynamic data for my tests, but have the following problem:
The view I'm testing is supposed to show me all the assignments a particular user has to complete. To test this, I want to create 500 assignments, log into the app and check if they are shown. So far I have the following test cases:
class TestLogin(TestCase):
def setUp(self):
self.client = Client()
user = User.objects.create(username='sam')
user.set_password('samspassword')
user.save()
def test_login(self):
self.client.login(username='sam', password='samspassword')
response = self.client.get('/')
print (response.content)
self.assertEqual(response.status_code, 200)
and
class TestShowAssignments(TestCase):
def setUp(self):
user_recipe = Recipe(User, username='sam', password="samspassword")
self.assignment = Recipe(Assignment,
coders = related(user_recipe))
self.assignments = self.assignment.make(_quantity=500)
def test_assignments(self):
self.assertIsInstance(self.assignments[0],Assignment)
self.assertEqual(len(self.assignments),500)
The first test passes fine and does what it should: TestLogin logs the user in and shows his account page.
The trouble starts with TestShowAssignments, which creates 500 assignments but if I look at the assignments with print (self.assignments[0].coders), I get auth.User.None. So it doesn't add the user I defined as a relation to the assignments. What might be important here is that the coders field in the model is a m2m field, which I tried to address by using related, but that doesn't seem to work.
What also doesn't work is logging in: if I use the same code I use for logging in during TestLogin in TestShowAssignments, I can't log in and see the user page.
So, my question: How do I use model_mommy to create Assignments and add them to a specific user, so that I can log in as that user and see if the assignments are displayed properly?
Do you want 500 Assignments that all have User "sam" as a single entry in the 'coders' field? If so, try:
from model_mommy import mommy
...
class TestShowAssignments(TestCase):
def setUp(self):
self.user = mommy.make(User, username='sam', password='samspassword')
self.assignments = mommy.make(Assigment, coders=[self.user], _quantity=500)