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 follow the instructions of their repository. 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.
# 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.
# 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 Overview section of their repository
Here is an example test that inserts a client when we need it. Calling the client_fixture
function.
# 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 following the instructions on their GitHub page and configure it with Chromedriver.
# 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.
# 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.
#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
. Here you can read the documentation.
# .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.
# .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.
# .github/workflows/ci.yml
- run: npm install
working-directory: ./assets
- run: npm run deploy --prefix ./assets
Run the tests
And finally, run the tests.
# .github/workflows/ci.yml
- run: mix test --trace
Overview
There is our entire ci.yml
file.
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 GitHub Actions workflows 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 Glenn Carstens-Peters on Unsplash