---
title: 'ExMachina for Elixir: Factories with a Functional Twist'
teaser: Announcing a new factory library for Elixir.
tags: elixir,testing,web
author:
- Josh Steiner
- Paul Smith
published_on: 2015-11-02
---

[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.

[ExMachina]: https://github.com/thoughtbot/ex_machina

```elixir
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...

```elixir
user = create(:user, name: "Kanye West")
articles = create_pair(:article, author: user)
```

...but flexible enough to use like this:

```elixir
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:

```elixir
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 FactoryBot[^1], 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.

[ExMachina docs]: http://hexdocs.pm/ex_machina/README.html

[^1]: Looking for FactoryGirl? The library was renamed in 2017.
[Project name history can be found here.](https://github.com/thoughtbot/factory_bot/blob/master/NAME.md)
