ExMachina for Elixir: Factories with a Functional Twist

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 FactoryGirl

If you’re coming from FactoryGirl, 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.