Phoenix: Integration tests at GitHub Actions
Tags: phoenix, github
Published: 2020-02-13 08:25:40

In this post, I will try to explain how to do integration tests for your **Phoenix project** and add them to the checks in your repository

These are the tools that we will use:

- **Ex_machina** to create test data.
- **Wallaby** to test your web applications by simulating realistic user interactions.
- **GitHub Actions** to configure the environment and run the tests at GitHub.

## ExMachina

The first step will be to configure your project to easily create test data. As mentioned above, we will use `ex_machina`. To install it <a href="https://github.com/thoughtbot/ex_machina" target="_blank">follow the instructions of their repository</a>. Once we have installed the library, let's configure it to our needs.

### Factories and Factory Module

We will split factories into separate files and will import them by creating a Factory module.

Here we have the code of our `Client` factory.

```elixir
# test/suport/factories/client.ex

defmodule OurProject.ClientFactory do
  defmacro __using__(_opts) do
    quote do
      def client_factory do
        %OurProject.Clients.Client{
          name: "Client Name",
          contact_info: "Client contact_info"
        }
      end
    end
  end
end
```

And then we import it in our Factory Module.

```elixir
# test/support/factory.ex

defmodule OurProject.Factory do
  use ExMachina.Ecto, repo: OurProject.Repo
  use OurProject.ClientFactory
end
```

Once we have created our Factory module and our factories we can use them in any test importing the factory module. You can with `ExMachina` you can build or insert test data, the main difference is that build returns an object that is not saved at the database. For more information read the <a href="https://github.com/thoughtbot/ex_machina#overview" target="_blank">Overview section</a> of their repository

Here is an example test that inserts a client when we need it. Calling the `client_fixture` function.

```elixir
# test/our_project/client/some_client_test.ex

defmodule OurProject.SomeClientTest do
  use OurProject.DataCase

  import OurProject.Factory

  def client_fixture(attrs \\ %{}) do
    # `insert*` returns an inserted comment. Only works with ExMachina.Ecto
    # Associated records defined on the factory are inserted as well.
    insert(:client, attrs)
  end
	...
end
```

## Wallaby

We already have our data so we can start with the integration tests. Install wallaby <a href="https://github.com/elixir-wallaby/wallaby" target="_blank">following the instructions on their GitHub page</a> and configure it with **Chromedriver**.

```elixir
# config/test.exs

config :wallaby,
  driver: Wallaby.Experimental.Chrome,
  chrome: [
    headless: true
  ]
```

Now, we will create a FeatureCase module to configure the database, create the session and import the modules and helpers that our tests will need. Here we will import our `Factory` module.

```elixir
# test/support/feature_case.ex

defmodule OurProject.FeatureCase do
  use ExUnit.CaseTemplate

  using do
    quote do
      use Wallaby.DSL
      import Wallaby.Query

      alias OurProject.Repo
      import Ecto
      import Ecto.Changeset
      import Ecto.Query

      #Import our Router Helpers
      import OurProject.Router.Helpers

      #Import custom helpers if we need them
      import OurProject.TestHelpers.Navigation

      #Import our ExMachina factories
      import OurProject.Factory
    end
  end

  # Connect to the test database and create the session for each test
  setup tags do
    :ok = Ecto.Adapters.SQL.Sandbox.checkout(OurProject.Repo)

    unless tags[:async] do
      Ecto.Adapters.SQL.Sandbox.mode(OurProject.Repo, {:shared, self()})
    end

    metadata = Phoenix.Ecto.SQL.Sandbox.metadata_for(OurProject.Repo, self())
    {:ok, session} = Wallaby.start_session(metadata: metadata)
    {:ok, session: session}
  end
end
```

Finally, we are ready to create our first test. We are going to create a test that requires fake data so we can see how to use the factories inside an integration test.

```elixir
#test/integration/client/delete_client.exs

defmodule OurProject.DeleteClientTest do
  use OurProject.FeatureCase, async: false

  setup %{session: session} do
    client = insert(:client)
    {:ok, session: session, client: client}
  end

  test "remove client", %{session: session, client: client} do
    session
    |> visit("/clients", client.name)
    |> click(link(client.name))
    |> click(link("delete"))
    |> assert_has(css(".toast-message", count: 1, text: "Client successfully deleted"))
  end
end
```

## GitHub Actions

We have our integration tests and we want to check them when we submit a pull request. With a few steps, we can configure our GitHub workflow to run the tests every time we push to the branch.

### Chromedriver

We have to add Chromedriver to our CI. To do it we will use an action created by `@nanasses`. <a href="https://github.com/nanasess/setup-chromedriver" target="_blank">Here you can read the documentation.</a>

```yaml
# .github/workflows/ci.yml

- uses: nanasess/setup-chromedriver@master
```

### Compile our project

We have to get the dependencies, compile the project and run the migrations.

```yaml
# .github/workflows/ci.yml

- name: Install Dependencies
  run: mix deps.get
- run: mix compile
- run: mix ecto.migrate
```

### Compile our assets

In order to have our javascript compiled it's needed to compile our assets.

```yaml
# .github/workflows/ci.yml

- run: npm install
  working-directory: ./assets
- run: npm run deploy --prefix ./assets
```

### Run the tests

And finally, run the tests.

```yaml
# .github/workflows/ci.yml

- run: mix test --trace
```

### Overview

There is our entire `ci.yml` file.

```yaml
name: Continuous Integration
on: [push]

# This workflow will build and test a Phoenix application that uses
# PostgreSQL as a database. Will also cache dependencies and build
# artifacts to speed up the build.
jobs:
  test:
    env:
      MIX_ENV: ci
      DATABASE_URL: postgres://postgres:postgres@localhost:5432/postgres
    runs-on: ubuntu-latest

    services:
      db:
        env:
          POSTGRES_USER: postgres
          POSTGRES_PASSWORD: postgres
          POSTGRES_DB: postgres
        image: postgres:11
        ports: ['5432:5432']
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5
    steps:
      - uses: actions/checkout@v1.0.0
      - name: Recover dependency cache
        uses: actions/cache@v1
        id: deps_cache
        with:
          path: deps
          key: ${{ runner.OS }}-deps-${{ hashFiles('**/mix.lock') }}
          restore-keys: |
            ${{ runner.OS }}-deps-${{ env.cache-name }}-
            ${{ runner.OS }}-deps-
            ${{ runner.OS }}-
      - name: Recover build cache
        uses: actions/cache@v1
        with:
          path: _build
          key: ${{ runner.OS }}-build-${{ env.cache-name }}
          restore-keys: |
            ${{ runner.OS }}-build-
            ${{ runner.OS }}-
      - uses: actions/setup-elixir@v1.0.0
        with:
          otp-version: 22.x
          elixir-version: 1.9.x
      - uses: nanasess/setup-chromedriver@master
      - name: Install Dependencies
        run: mix deps.get
      - run: mix compile
      - run: mix ecto.migrate
      - run: npm install
        working-directory: ./assets
      - run: npm run deploy --prefix ./assets
      - run: mix test --trace
```

## That's it

We have our tests created and running on GitHub. If you are interested in GitHub Actions, at Codegram we have a repository with <a href="https://github.com/codegram/useful-github-workflows/" target="_blank">GitHub Actions workflows</a> that can be useful to you.

I hope you found this post useful. If you have any comments, questions or suggestions, tell us on Twitter!

Photo by <a href="https://unsplash.com/@glenncarstenspeters" target="_blank">Glenn Carstens-Peters</a> on <a href="https://unsplash.com" target="_blank">Unsplash</a>