Integration Testing Phoenix Applications
TL;DR Check out phoenix_integration for server-side integration testing your Phoenix applications. It’s cool.
Comprehensive tests that are easy to read, maintainable and fast are critical to any sizable application. We are fortunate that Elixir’s ExUnit and Phoenix.ConnTest provide such a strong base to work on. Still, There are (at least) four major levels of testing required in any sizable web-style app.
Plenty has been written about Function and Controller level testing in Phoenix. Just read the excellent book Programming Phoenix for a good overview.
Until recently, however, there was a hole at the Server Integration testing level. In the Ruby on Rails world, this need was served by Capybara. I used Capybara, but was never quite satisfied with the readability, maintainability and especially speed of the resulting tests. Ideally, integration tests are something you write before your controller tests, not dread because they are a pain and slow the test suite down too much.
To me, a good Server Integration tool should have the following properties (in order of importance to me):
- Fast. Similar to Controller testing, it should go through the router, but not necessarily the Cowboy (or other) server. Running in parallel is a requirement here.
- Readable. Integration tests can get rather long and need to be both readable and understandable.
- Maintainable. Closely related to readability, integration tests often need to be maintained as the contents of your application change. They must be understandable to be maintainable.
- Multiple simulated users. Integration tests often need to simulate the interactions between multiple users. So it must be able to be clear about who is making which calls
These are the primary goals of phoenix_integration. Class name: PhoenixIntegration. It is intended to enable the Server Integration class of tests, emphasizing speed, and readability.
I love the pipe |> command in Elixir. By using the pipe to chain together calls in an integration test, phoenix_integration is able to be very readable. Tight integration with Phoenix.ConnTest means the call all use the fast-path to your application for speed.
Each function in phoenix_integration accepts a conn and some other data, and returns a conn. This conn is intended to be passed into the next function via a pipe. to build up a clear, readable chain of events in your test.
In general, the functions look for links or forms in the html returned by a previous request. Then they make a new request to application as specified by your test. If the link wasn’t found, then an appropriate error is raised.
See the full documentation for details.
For example, a call such as follow_link( conn, “About Us” ), looks in conn.body_request (which should contain html from a previous request), for an anchor tag that contains the visible text ‘About Us’. Note that it uses =~ and not == to look for the text, so you only need to specify enough text to find the link.
The follow_form function finds a form in the body of the previously returned conn, fills in the fields you have specified (raising an appropriate error if the form or fields aren’t found), submits the form to your application, and follows any redirects.
Tracking multiple users
A very common scenario involves interactions between multiple users. The good news is that user state is returned in the conn from your controllers, so it is easy to track.
Is this example, I use a test_sign_in_user function (not shown), which uses token authentication so that I don’t have to pay the BCrypt price every time I run a test…
I really wanted to see unbroken chains of piped call to make it really clear that this was a chain of events/state being tested.
The following line, which is very common in Phoenix.ConnTest controller tests works well, but doesn’t allow you to build that chain of commands.
assert html_response(conn, 200) =~ “Some text”
So, the PhoenixIntegration.Assertions module introduces two new functions, which can test multiple conditions in a single call, and always return the (unchanged) conn being tested.
I use assert_response at almost a 1:1 ratio with the various request calls, so my tests often look something like this:
See the full documentation for details.
Testing your application is extremely important. When I first started building applications (back in the Rails days), I focused almost entirely on Model and Controller tests. Over time, I came to appreciate the fact that integration tests are much more thorough and surface more interesting bugs in your code.
Controller tests are important too! But they should really focus on small, targeted tests that especially confirm the negative conditions for each of your actions.
In the past, even while valuing integration tests, I dreaded them too because they were both slow, and hard to read and maintain.
PhoenixIntegration uses the advantages of the Elixir and Phoenix designs to create readable integration tests that are really fast to run. (as long as you aren’t constantly calculating BCrypt password digests)
I hope this helps your applications too.
If this was helpful, check out my tutorial on how to use Policy Wonk to do authorization in Phoenix.