Functional Testing

We can add functional tests to our application to verify all of the functionality that we claim to have. Ferris provides you with the ability to test our application’s controllers in the same way that we tested our data model.

Redirect Routing

Before we get started testing, we need to add a root route so that http://localhost:8080/ goes to /posts.

Modify app/routes.py and remove these lines:

routing.default_root()

Add these lines in their place:

routing.redirect('/', to='/posts')

At this point opening http://localhost:8080/ should send us to http://localhost:8080/posts.

Note

Tests are run using the nose plugin. See the docs on Testing for instructions on how to install and use the nose plugin.

The Sanity Test

Ferris provides an example test called SanityTest that resides in app/tests/backend/test_sanity.py.

Here’s the code for that test:

from ferrisnose import FerrisAppTest

class SanityTest(FerrisAppTest):

    def testRoot(self):
        self.loginUser()
        resp = self.testapp.get('/')

        self.loginUser(admin=True)
        resp = self.testapp.get('/admin')
        self.assertTrue('Ferris' in resp)

Let’s walk through this:

  • We create a test class by inheriting from FerrisAppTest. FerrisAppTest handles loading and registering all of our controllers. It also gives you a test environment that is very similar to your full application.
  • We start our test method by logging in a user. We can specify an email and admin status, or we can use the default setup of test@example.com as a regular user.
  • Our test makes a GET request to ‘/’, our root url. If this fails to return a 200 OK response, our test will fail.
  • Then we log in as an admin user and do a GET request to ‘/admin’ and make sure that “Ferris” is somewhere in the body of the response.

We’re going build on this example and create tests to verify:

  • /posts is a list of all user’s posts.
  • /posts?mine is a list of only the logged-in user’s posts.
  • /posts contains edit links.
  • /posts/add is a form and submitting that form will create a new item.
  • /posts/edit doesn’t allow users to edit posts that aren’t theirs.

While this isn’t an exhaustive list, it will demonstrate how to test various aspects of our application.

Testing Lists

In this test, we’ll create four posts: two from the first user, and two from the second user. We will then check the results of our list methods. Our tests should verify that the data was created and appears as expected.

First, let’s create our test class in app/tests/backend/test_posts.py:

from ferrisnose import FerrisAppTest
from app.models.post import Post


class TestPosts(FerrisAppTest):

    def testLists(self):
        self.loginUser("user1@example.com")

We have a user logged in, so let’s create some posts as that user:

Post(title="Test Post 1").put()
Post(title="Test Post 2").put()

Now let’s log in the second user and create some more posts:

self.loginUser("user2@example.com")

Post(title="Test Post 3").put()
Post(title="Test Post 4").put()

At this point we can now make requests and verify their content. Let’s start with /posts and verify that all of the posts are showing up:

resp = self.testapp.get('/posts')

assert 'Test Post 1' in resp.body
assert 'Test Post 2' in resp.body
assert 'Test Post 3' in resp.body
assert 'Test Post 4' in resp.body

Let’s continue with /posts?mine and verify that only user2@example.com's posts are present:

resp = self.testapp.get('/posts?mine')

assert 'Test Post 1' not in resp.body
assert 'Test Post 2' not in resp.body
assert 'Test Post 3' in resp.body
assert 'Test Post 4' in resp.body

Additionally, let’s make sure the ‘edit’ links are present:

assert 'Edit' in resp.body

Testing Add

Let’s add a new method and make a request to /posts/add:

def testAdd(self):
    self.loginUser("user1@example.com")

    resp = self.testapp.get('/posts/add')

Now let’s get the form from the response, try to submit it without filling it out, and verify that it caused a validation error:

form = resp.form
error_resp = form.submit()

assert 'This field is required' in error_resp.body

With that in place, let’s fill out the form, submit it, and verify that it went through:

form['title'] = 'Test Post'
good_resp = form.submit()

assert good_resp.status_int == 302  # Success redirects us to list

Finally, load up the list and verify that the new post is there:

final_resp = good_resp.follow()

assert 'Test Post' in final_resp

Testing Edit

To test to make sure that a user can only edit his own posts, we’re going to need to create posts under two different users like we did before:

def testEdit(self):
    self.loginUser("user1@example.com")

    post_one = Post(title="Test Post 1")
    post_one.put()

    self.loginUser("user2@example.com")

    post_two = Post(title="Test Post 2")
    post_two.put()

Now, let’s load the edit page for post two. This should succeed:

self.testapp.get('/posts/:%s/edit' % post_two.key.urlsafe())

Finally, load the edit page for post one. We should expect this to fail:

self.testapp.get('/posts/:%s/edit' % post_one.key.urlsafe(), status=403)