ExMachina makes it easy to generate Elixir test data using factories. It works out of the box with Ecto associations and embeds, and can be easily composed to make creating data extremely flexible.
defmodule MyApp.Factory do
use ExMachina.Ecto, repo: MyApp.Repo
def factory(:user, _attrs) do
%User{
name: "Jane Smith",
email: sequence(:email, fn(n) -> "email-#{n}@example.com" end)
}
end
def factory(:article, attrs) do
%Article{
title: "Use ExMachina!",
# Use the :author from the attrs if it exists, otherwise build a new :user
author: assoc(attrs, :author, factory: :user)
}
end
end
ExMachina is simple enough to use like this…
user = create(:user, name: "Kanye West")
articles = create_pair(:article, author: user)
…but flexible enough to use like this:
def make_admin(user) do
%{user | admin: true}
end
build(:user) |> make_admin |> create
Flexibility is a feature
ExMachina strives to use plain old functions as much as possible. This makes it easy to use and extend, without the need to learn a complicated DSL. ExMachina works with Ecto out of the box, but it’s easy to extend in other ways.
You can customize ExMachina to save records however you need by creating a
save_record/1
function:
defmodule MyApp.JsonFactory do
use ExMachina
def factory(:user, _attrs) do
%{name: "John"}
end
# This will be called when using `create`
def save_record(record) do
Poison.encode!(record)
end
end
# Builds and returns a JSON encoded version of the map
MyApp.JsonFactory.create(:user)
Why factories?
While writing your tests you will need a way to set up test data. You could
insert them with changesets or directly through Repo.insert
, but that gets
tedious when you have many validations and constraints on your model. When
inserting records like this, you have to specify attributes to fulfill the
validations, even if your test has nothing to do with those validations. On top
of that, if you ever change your validations later, you have to reflect those
changes across every test in your suite. The solution is to use either factories
or fixtures to create records.
Fixtures work by creating static data in a file that can be used across all of
your tests. For example, you might start off with unpublished_article
and
published_article
. Later on, you need to test the functionality of articles
with comments. Since fixtures are not composable, you must declare new fixtures
for published_article_with_comments
and unpublish_article_with_comments
.
This can lead to a lot of duplication and brittle tests.
With factories, you define the most basic data that creates a valid record. In
your tests, you can then explicitly override the attributes that are important
to the test. For example, you can do create(:article, published: true)
. If you
need to add comments, you can now extend this with create(:article,
published: true) |> with_comments
. This allows for explicit tests without
duplication.
One of the major drawbacks of factories in other languages is factories’ inherent slowness (at least when compared to fixtures). Elixir and Ecto are fast enough that this isn’t a problem.
Improving on FactoryBot
If you’re coming from FactoryBot1, ExMachina will feel like home. That said, ExMachina does have some improvements:
build
does not create associated records. This keeps your tests lean and fast.- No need to add extra DSL for things like traits and aliases, just use functions and piping.
Check out the ExMachina docs for more in depth examples. We hope you love using ExMachina.
Project name history can be found here.
-
Looking for FactoryGirl? The library was renamed in 2017. ↩