21 June 2021
How to get Elixir and Phoenix up and running
Maybe you’ve recently read an awesome blog post about Elixir and would like to try it out for yourself or you are simply trying to get back into it after taking a hiatus(the boat I’m in). This post is aimed at Elixir newbies who would like to get up and running with a new project that has integration tests configured and some basic CI with GitHub Actions.
Install Elixir and Erlang
First things first, you need to make sure you have Elixir and Erlang installed. At Gaslight, we recommend using asdf to manage versions. It eliminates the need for multiple version managers(think rbenv, nvm
, etc. combined) and allows for global and local, project-specific tool versions. Installation instructions can be found here.
At the time of writing this post, v1.12.0 is the latest version of Elixir and v24.0.0 is the latest version of Erlang/OTP. Go ahead and install these versions and set them globally
asdf plugin-add elixir
asdf plugin-add erlang
asdf install erlang 24.0.0
asdf global erlang 24.0.0
asdf install elixir 1.12.0
asdf global elixir 1.12.0
Verify you have the correct versions installed:
erl -v
elixir -v
If you see Erlang/OTP 24
and Elixir 1.12.0 (compiled with Erlang/OTP 22)
after running those commands, you’re good to go. If not, I’d recommend checking StackOverflow to see if someone else has had the same issue.
Generate a New Project
Next up you’ll want to make sure you have phx_new
installed so you can generate a new project.
mix archive.install hex phx_new 1.5.9
Once you have phx_new
installed, you’re ready to generate a new project. There are several options when creating a new phoenix project but for simplicity, we’re going to use the base phx.new
command. You can find all of the flags and options for generating a new project here.
Go ahead and generate a new project:
mix phx.new hello_world
When prompted to fetch and install dependencies, enter y
. cd
into the directory and create your database. If you’ve never installed Postgres, here’s a solid post about getting it set up. cd hello_world && mix ecto.create
Start your application to make sure it’s working properly by going to https://localhost:4000/
mix phx.server
Commit to GitHub
Congrats! You now have a working Elixir/Phoenix application. Now let’s get it committed to GitHub so we can start setting up integration tests.
git init
git add .
One trick I learned from a fellow developer at Gaslight was to make your first commit the command you ran to generate your project so you don’t lose track of the flags you used.
git commit -m "mix phx.new hello_world"
git branch -M master
git remote add origin https://github.com/atuley/hello_world.git
git push -u origin master
Add Integration Test Library
Perfect. Now we have source control and can begin setting up your integration tests. For this post, we will use Wallaby.
Add Wallaby as a dependency in your deps
function inside of mix.exs
defp deps do
[
…,
{:wallaby, "~> 0.28.0", runtime: false, only: :test}
]
end
Fetch your dependencies
mix deps.get
Configure Wallaby
Next, we need to make some adjustments to your config/test.exs
file to account for Wallaby. Under your Endpoint
configuration, toggle server
to true
config :hello_world, HelloWorldWeb.Endpoint,
https: [port: 4002],
server: true
We also need to configure Wallaby to use chromedriver. Add this to the bottom of your config/test.exs
file.
config :wallaby,
driver: Wallaby.Chrome,
otp_app: :hello_world,
chromedriver: [
headless: false
]
config :hello_world, :sandbox, Ecto.Adapters.SQL.Sandbox
After that, we need to ensure Wallaby is started up whenever mix test
is run. Add the following lines to your test/test_helper.exs
file.
{:ok, _} = Application.ensure_all_started(:wallaby)
Application.put_env(:wallaby, :base_url, HelloWorldWeb.Endpoint.url)
Now we want to enable concurrent testing by adding the Phoenix.Ecto.SQL.Sandbox
to your Endpoint
(endpoint.ex). Make sure this is before any other plugs in the file.
if sandbox = Application.get_env(:hello_world, :sandbox) do
plug Phoenix.Ecto.SQL.Sandbox, sandbox: sandbox
end
To recompile assets when you run mix test
you will need to add an alias to your mix config that does this. Wallaby has provided a small function to do this in their documentation:
Adjust your test aliases in mix.exs
and add compile_assets/1
test: [
"assets.compile --quiet",
"ecto.create --quiet",
"ecto.migrate",
"test",
],
"assets.compile": &compile_assets/1
]
defp compile_assets(_) do
Mix.shell().cmd("cd assets && ./node_modules/.bin/webpack --mode development",
quiet: true
)
end
More information about configuration and troubleshooting can be found in their documentation.
Writing your First Wallaby Test
Now that we have everything configured, we need to make sure it’s working as expected. Let’s write a simple test that checks for text on the home page.
First, let’s create a new folder under test/hello_world_web/
called feature
to house your integration tests. Create a new file and name it what you’d like. Make sure it ends with _test.exs
so it can get picked up by mix test
. I’ll call mine first_feature_test.exs
(naming is hard :D)
Add the following code to first_feature_test.exs
defmodule HelloWorldWeb.FirstFeatureTest do
use ExUnit.Case, async: true
use Wallaby.Feature
feature "visit the home page", %{session: session} do
session
|> visit("/")
|> assert_has(Query.css(".welcome-message", text: "Welcome to Phoenix!"))
end
end
Make sure you add the .welcome-message
class to index.html.eex
to get the test to pass.
<h1 class="welcome-message"><%= gettext "Welcome to %{name}!", name: "Phoenix" %></h1>
session
|> visit("/")
….
Navigates to the home page,
assert_has(Query.css(".welcome-message", text: "Welcome to Phoenix!”))
Asserts the home page has a welcome message by querying for a css class and checks the associated text. If it does not find the provided text, it will raise an error and fail.
Here’s a link to Wallaby’s API documentation if you’re curious what other browser tools you have at your disposal. Now that we have our test, we can go ahead and run it with mix test test/hello_world_web/feature/first_feature_test.exs
.
Your test should pass. If you’ve never installed chromedriver you may be experiencing some issues. You can check to see if you have it installed by running chromedriver —version
. If you get nothing, you’ll need to install it. I recommend installing with homebrew.
If you’d prefer your tests to not open up a chrome window when running, you can run them in headless mode by changing headless to true
in config/test.exs
config :wallaby,
driver: Wallaby.Chrome,
otp_app: :hello_world,
chromedriver: [
headless: true
]
At this point I’d recommend making another commit before we start setting up GitHub Actions.
git add .
git commit -m “Setup Wallaby”
git push origin master
Here’s a link to the HelloWorld project and the commit for setting Wallaby up.
Setting Up Continuous Integration with GitHub Actions
GitHub Actions are an easy way to get up and running with continuous integration without having to use third-party tools like CircleCI(https://circleci.com/). GitHub has some default workflows that provide a good starting point. If you navigate to the “Actions” tab in your repo, it will more than likely already be suggesting to add an Elixir workflow. If you just want to build and run unit tests, this is a good option. If you want your Wallaby tests to run as well, you’ll need to do some extra configuration.
We’ll use GitHub’s Elixir starter workflow as our base. Go ahead and create a new folder at the root of your project called .github/workflows
and add the following elixir.yml
file.
name: Elixir CI
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
build:
name: Build and test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Elixir
uses: erlef/setup-elixir@885971a72ed1f9240973bd92ab57af8c1aa68f24
with:
elixir-version: ‘1.12.0’ # Define the elixir version [required]
otp-version: '22.3' # Define the OTP version [required]
- name: Restore dependencies cache
uses: actions/cache@v2
with:
path: deps
key: $
restore-keys: $-mix-
- name: Install dependencies
run: mix deps.get
- name: Run tests
run: mix test
Be sure to update the elixir-version
with the version you are using in your project.
with:
elixir-version: ‘1.12.0’
In order to use our test database, we need to configure a postgres service container. Add the following under jobs:
test:
env:
MIX_ENV: test
DATABASE_URL: postgres://postgres:postgres@localhost:5432/postgres
runs-on: ubuntu-latest
services:
db:
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: hello_world_test
image: postgres:11
ports: ['5432:5432']
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
Be sure to update the POSTGRES_DB
field with your test database name. This is typically your application name with _test
added at the end. It can be found in
# config/text.exs
database: "hello_world_test#{System.get_env("MIX_TEST_PARTITION”)}”,
Next, we specify that we need to use chromedriver like we did locally. Add the following above - name: Install Dependencies
...
- uses: nanasess/setup-chromedriver@master
- name: Install Dependencies
...
Finally, we need to make sure our application compiles all assets and runs migrations before testing. Add the following under run: mix deps.get
- run: mix compile
- run: mix ecto.migrate
- run: npm install
working-directory: ./assets
-run: npm run deploy --prefix ./assets
Make sure your tests are configured to run in headless mode in your config/test.exs
file.
config :wallaby,
driver: Wallaby.Chrome,
otp_app: :hello_world,
chromedriver: [
headless: true
]
If you want a separate configuration for CI, you can create another file under config
called ci.exs
and add your configuration there. Just make sure you change your MIX_ENV
to ci
.
Now commit our .github/workflows/elixir.yml
file and navigate to your repository in GitHub. Under “Actions” you should see your latest commit running and eventually should pass. You should now be in a good place to start writing new features for your application!
If you’d like to add a badge with your applications build status to your README.md, just simply add
![example workflow](https://github.com/<username>/<application>/actions/workflows/elixir.yml/badge.svg)
Here is the link to the full configuration file if you’d like to copy/paste: https://github.com/atuley/hello_world/blob/master/.github/workflows/elixir.yml
Repository link: https://github.com/atuley/hello_world