Getting Started with ExMachina
This guide walks you through installing ExMachina, defining your first factory, and using it in tests.
Installation
Add ex_machina to your test dependencies in mix.exs:
defp deps do
[
{:ex_machina, "~> 2.8.0", only: :test}
]
end
Then fetch dependencies:
mix deps.get
Start ExMachina in your test/test_helper.exs:
{:ok, _} = Application.ensure_all_started(:ex_machina)
ExUnit.start()
Project Setup
ExMachina factories are typically placed in test/support/factory.ex. For non-Phoenix projects, you need to tell the compiler to include that directory during tests:
# mix.exs
def project do
[
app: :my_app,
elixirc_paths: elixirc_paths(Mix.env()),
# ...
]
end
defp elixirc_paths(:test), do: ["lib", "test/support"]
defp elixirc_paths(_), do: ["lib"]
Phoenix projects already have this configured.
Define Your Factory Module
With Ecto
If your project uses Ecto, use ExMachina.Ecto and pass your repo:
# test/support/factory.ex
defmodule MyApp.Factory do
use ExMachina.Ecto, repo: MyApp.Repo
def user_factory do
%MyApp.User{
name: "Jane Smith",
email: sequence(:email, &"user-#{&1}@example.com"),
age: 30
}
end
end
Without Ecto
For projects without Ecto, use the base ExMachina module:
defmodule MyApp.Factory do
use ExMachina
def user_factory do
%{
name: "Jane Smith",
email: sequence(:email, &"user-#{&1}@example.com"),
age: 30
}
end
end
Naming Convention
Factory functions follow the pattern <name>_factory. The name you pass to build, insert, and other helpers maps directly to these functions:
build(:user)callsuser_factory/0build(:blog_post)callsblog_post_factory/0insert(:comment)callscomment_factory/0
Building Data
Import your factory in tests and use build to create in-memory data:
defmodule MyApp.UserTest do
use ExUnit.Case
import MyApp.Factory
test "build creates a user struct" do
user = build(:user)
assert user.name == "Jane Smith"
assert user.email =~ "user-"
end
end
Override Attributes
Pass a keyword list or map to override defaults:
user = build(:user, name: "Custom Name", age: 25)
assert user.name == "Custom Name"
assert user.age == 25
Build Multiple Records
# Build exactly two
[user1, user2] = build_pair(:user)
# Build a specific number
users = build_list(5, :user, role: "admin")
assert length(users) == 5
Inserting into the Database
When using ExMachina.Ecto, the insert family of functions builds a record and saves it via Repo.insert!:
# Insert one record
user = insert(:user)
# Insert with overrides
admin = insert(:user, role: "admin")
# Insert two
[user1, user2] = insert_pair(:user)
# Insert many
users = insert_list(10, :user)
You can pass Ecto repo options as a third argument:
insert(:user, [name: "Jane"], prefix: "tenant_one")
insert(:user, [name: "Jane"], returning: true)
Generating Params
For controller and API tests, use params functions to get plain maps:
# Atom keys, no Ecto metadata
params = params_for(:user, name: "Test User")
# => %{name: "Test User", email: "user-0@example.com", age: 30}
# String keys (for Phoenix controller params)
params = string_params_for(:user, name: "Test User")
# => %{"name" => "Test User", "email" => "user-0@example.com", "age" => 30}
The _with_assocs variants insert any belongs_to associations and include the foreign keys:
# Inserts the associated author and includes author_id
params = params_with_assocs(:article)
# => %{title: "...", author_id: 1}
string_params = string_params_with_assocs(:article)
# => %{"title" => "...", "author_id" => 1}
Sequences
Use sequence to generate unique values that increment across calls:
def user_factory do
%MyApp.User{
email: sequence(:email, &"user-#{&1}@example.com"),
username: sequence(:username, &"user#{&1}")
}
end
Sequences also support cycling through a list:
def user_factory do
%MyApp.User{
role: sequence(:role, ["admin", "editor", "viewer"])
}
end
And custom starting values:
sequence(:id, &"id-#{&1}", start_at: 100)
# => "id-100", "id-101", "id-102", ...
Derived Factories
Create variations of a factory by calling the original and modifying the result:
def admin_factory do
struct!(user_factory(), %{role: "admin", permissions: ["all"]})
end
# Usage
admin = build(:admin)
Flexible Factory Patterns
Write helper functions that transform factory output — ExMachina works well with pipeline composition:
def make_admin(user), do: %{user | role: "admin"}
def with_posts(user) do
insert_list(3, :post, author: user)
user
end
# Chain together
user = build(:user) |> make_admin() |> insert() |> with_posts()
Next Steps
- Working with Associations — Handle belongs_to, has_many, and other Ecto relationships
- Custom Strategies — Build strategies for JSON encoding, API calls, and more
- Cheatsheet — Quick reference for all functions