Painless Phoenix Feature Tests

Writing and maintaining feature tests can be very difficult, but they also provide an invaluable
measure of confidence that your application works as expected. By following a few best practices,
you can keep your feature test suite under control.

A note on terminology: I use the term feature test to mean an automated test that drives a
browser through the application. Some prefer the terms end-to-end test, acceptance test or
integration test. For most purposes, these terms are used interchanably.

Use Page Modules

Page modules offer the perfect abstraction between your test cases and your application’s behavior.
They allow your tests to be clear an concise. To illustrate, let’s take a simple test case for
creating a blog post. This is what our test might look like:

describe"creating a new post"dotest"succeeds with valid attributes"dopost_attrs=%{title:"Test Title",body:"Test Body"}navigate_to"/posts/new"fill_field({:id,"title-field"},post_attrs.title)fill_field({:id,"body-field"},post_attrs.body)click({:class,"submit-button"})accept_dialog()assertRepo.one(Post)endend

You’ll notice that the test above has more to do with interacting with the DOM than with testing the
functionality we care about. If we think about what we’re specifically trying to test, then there are
only three steps of importance here:

Build a set of post attributes

Submit the post attributes

Assert that the post was saved

By introducing the concept of Page Modules, We can hide the DOM code and provide a clear API that
describes the functionality we need. Let’s rewrite the above example with page modules:

describe"creating a new post"dotest"succeeds with valid attributes"dopost_attrs=%{title:"Test Title",body:"Test Body"}# 1. Build a set of post attributesPostNewPage.submit(post_attrs)# 2. Submit the post attributesassertRepo.one(Post)# 3. Assert that the post was savedendend

The test that utilizes a page module is much easier to reason about because it hides most of the
details behind an explicit API.

In the above example, we introduced the page module PostNewPage. It looks like this:

Embrace Asynchronous Assertions

Sometimes the thing you’re trying to assert on doesn’t happen right away, especially if there’s
Javascript involved. In the test above, the assertion assert Repo.one(Post) might fail because
the submission hasn’t yet reached the database.

To make assertions more robust, we can make use of an eventually helper function. It takes a
function and simply calls it repeatedly until the assertions pass. If the assertions don’t pass in
the allotted time, the test fails.

Prefer More Focused Tests

The key to keeping your feature tests under control is to keep them focused. It can be tempting to
want your tests to drive through large parts of your application at once. It’s much easier to
maintain your feature tests over time if they’re small and only test one piece of functionality. Use
setup blocks to perform any complex setup, and only test one action at a time.

Stick With It

Imagine having the confidence to make major changes to your application and never feel the need to
click though and make sure you didn’t break anything. That’s the confidence that a well-built
feature test suite can provide. Feature testing can often be a frustrating process, and yes, it will
slow down your development speed at first. However, spending the time up front will pay dividends in
the long run.