PhoenixIntegration (phoenix_integration v0.9.2) View Source

Lightweight server-side integration test functions for Phoenix. Works within the existing Phoenix.ConnTest framework and emphasizes both speed and readability.

Configuration

Step 1

You need to tell phoenix_integration which endpoint to use. Add the following to your phoenix application's config/test.exs file.

config :phoenix_integration,
  endpoint: MyApp.Endpoint

Where MyApp is the name of your application.

Do this up before compiling phoenix_integration as part of step 2. If you change the endpoint in the config file, you will need to recompile the phoenix_integration dependency.

Phoenix_integration will produce warnings if your HTML likely doesn't do what you meant. (For example, it will warn you if two text fields have the same name.) You can turn those off by adding warnings: false to the config.

Step 2

Add PhoenixIntegration to the deps section of your application's mix.exs file

defp deps do
  [
    # ...
    {:phoenix_integration, "~> 0.8", only: :test}
    # ...
  ]
end

Don't forget to run mix deps.get

Step 3

Create a test/support/integration_case.ex file. Mine simply looks like this:

defmodule MyApp.IntegrationCase do
  use ExUnit.CaseTemplate

  using do
    quote do
      use MyApp.ConnCase
      use PhoenixIntegration
    end
  end

end

Alternately you could place the call to use PhoenixIntegration in your conn_case.ex file. Just make sure it is after the definition of @endpoint.

Overview

phoenix_integration provides two assertion and six request functions to be used alongside the existing get, post, put, patch, and delete utilities inside of a Phoenix.ConnTest test suite.

The goal is to chain together a string of requests and assertions that thouroughly exercise your application in as lightweight and readable manner as possible.

Each function accepts a conn and some other data, and returns a conn intended to be passed into the next function via a pipe.

Examples

test "Basic page flow", %{conn: conn} do
  # get the root index page
  get( conn, page_path(conn, :index) )
  # click/follow through the various about pages
  |> follow_link( "About Us" )
  |> follow_link( "Contact" )
  |> follow_link( "Privacy" )
  |> follow_link( "Terms of Service" )
  |> follow_link( "Home" )
  |> assert_response( status: 200, path: page_path(conn, :index) )
end

test "Create new user", %{conn: conn} do
  # get the root index page
  get( conn, page_path(conn, :index) )
  # click/follow through the various about pages
  |> follow_link( "Sign Up" )
  |> follow_form( %{ user: %{
        name: "New User",
        email: "user@example.com",
        password: "test.password",
        confirm_password: "test.password"
      }} )
  |> assert_response(
      status: 200,
      path: page_path(conn, :index),
      html: "New User" )
end

Simulate multiple users

Since all user state is held in the conn that is being passed around (just like when a user is hitting your application in a browser), you can simulate multiple users simply by tracking separate conns for them.

In the example below, I'm assuming an application-specific test_sign_in function, which itself uses the follow_* functions to sign a given user in.

Notice how user_conn is tracked and reused. This keeps the state the user builds up as the various links are followed, just like it would be when a proper browser is used.

Example

test "admin grants a user permissions", %{conn: conn, user: user, admin: admin} do
  # sign in the user and admin
  user_conn = test_sign_in( conn, user )
  admin_conn = test_sign_in( conn, admin )

  # user can't see a restricted page
  user_conn = get( user_conn, page_path(conn, :index) )
  |> follow_link( "Restricted" )
  |> assert_response( status: 200, path: session_path(conn, :new) )
  |> refute_response( body: "Restricted Content" )

  # admin grants the user permission
  get( admin_conn, page_path(conn, :index) )
  |> follow_link( "Admin Dashboard" )
  |> follow_form( %{ user: %{
        permissoin: "ok_to_do_thing"
      }} )
  |> assert_response(
      status: 200,
      path: admin_path(conn, :index),
      html: "Permission Granted" )

  # the user should now be able to see the restricted page
  get( user_conn, page_path(conn, :index) )
  |> follow_link( "Restricted" )
  |> assert_response(
      status: 200,
      path: restricted_path(conn, :index),
      html: "Restricted Content"
    )
end

Tip

You can intermix IO.inspect calls in the pipe chain to help with debugging. This will print the current state of the conn into the console.

test "Basic page flow", %{conn: conn} do
  # get the root index page
  get( conn, page_path(conn, :index) )
  |> follow_link( "About Us" )
  |> IO.inspect
  |> follow_link( "Home" )
  |> assert_response( status: 200, path: page_path(conn, :index) )
end

I like to use assert_response pretty heavily to make sure the content I expect is really there and to make sure I am traveling to the right locations.

  test "Basic page flow", %{conn: conn} do
    get(conn, page_path(conn, :index) )
    |> assert_response(
        status: 200,
        path: page_path(conn, :index),
        html: "Test App"
      )
    |> follow_link( "About" )
    |> assert_response(
        status: 200,
        path: about_path(conn, :index),
        html: "About Test App"
      )
    |> follow_link( "Contact" )
    |> assert_response(
        status: 200,
        path: about_path(conn, :contact),
        html: "Contact"
      )
    |> follow_link( "Home" )
    |> assert_response(
        status: 200,
        path: page_path(conn, :index),
        html: "Test App"
      )
  end

What phoenix_integration is NOT

phoenix_integration is not a client-side acceptence test suite. It does not use a real browser and does not exercise javascript code that lives there. It's focus is on fast, readable, server-side integration.

Try using a tool like Hound for full-stack integration tests.