Library / SDK
beam-community/ex_machina avatar
beam-community/ex_machina

ExMachina: factory-based test data for Elixir and Ecto

Create test data for Elixir applications

2,061 stars141 forksElixirMIT

At a glance

What is it?
ExMachina is an MIT-licensed Elixir library that generates test records through factory modules, with an Ecto path for inserting associated records and a non-Ecto path for any persistence layer. The library's eager evaluation of build is the first trap most users encounter.
Who is it for?
ExMachina suits Elixir applications that use Ecto and have grown past a handful of tests. The factory pattern keeps test setup out of test bodies and isolates model field changes to a single file.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 26 days ago.
What is it written in?
Mainly Elixir, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 11, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Test factories eliminate duplicated setup code across a growing test suite

Test suites that grow past a dozen models hit a common problem: each test needs realistic data, and constructing records inline spreads the same boilerplate across hundreds of files. Changing a field on a model requires editing every test that built that record by hand. ExMachina addresses this by collecting test data creation into factory modules. A factory defines default values for a struct, and any test that needs a user or an article calls build(:user) or insert(:article) rather than assembling the record from scratch.

The target audience is Elixir developers building applications with ExUnit. The Ecto integration is the primary draw: a factory using use ExMachina.Ecto can build in-memory structs, insert records with their associations, and produce plain maps for Phoenix controller tests. When a project has no Ecto dependency, use ExMachina gives the build path only, and no database access is involved.

The library is configurable to work with any persistence layer, though the documentation covers Ecto in depth. The project homepage is at https://hex.pm/packages/ex_machina, and the API documentation lives at https://hexdocs.pm/ex_machina/. The topics listed on the repository are elixir, factories, factory-definitions and testing.

Adding ex_machina to mix.exs and starting it before ExUnit

The dependency belongs in the test environment only. In mix.exs, add the ex_machina entry to the deps function:

elixir
def deps do
  [
    {:ex_machina, "~> 2.8.2", only: :test},
  ]
end

The only: :test key keeps the library out of production builds. After fetching the dependency, call Application.ensure_all_started(:ex_machina) in test/test_helper.exs before the ExUnit.start call. The order matters: ExMachina must start before ExUnit.

Placing factory modules in test/support ensures they compile only during test runs. Phoenix projects include test/support in their compilation paths by default. For non-Phoenix projects, the mix.exs project definition needs an elixirc_paths function that adds ["lib", "test/support"] when Mix.env is :test and ["lib"] otherwise:

elixir
defp elixirc_paths(:test), do: ["lib", "test/support"]
defp elixirc_paths(_), do: ["lib"]

The version constraint ~> 2.8.2 tracks the 2.8.x patch series. v2.8.2 was published on 2026-08-02, and v2.8.1 preceded it on 2026-07-14.

Factory modules use struct literals, the sequence helper and embedded associations

A factory module calls use ExMachina.Ecto, repo: MyApp.Repo and exports one function per record type, following the convention of a _factory suffix:

elixir
defmodule MyApp.Factory do
  use ExMachina.Ecto, repo: MyApp.Repo

  def user_factory do
    %MyApp.User{
      name: "Jane Smith",
      email: sequence(:email, &"email-#{&1}@example.com"),
      role: sequence(:role, ["admin", "user", "other"]),
    }
  end
end

The sequence/2 helper generates unique values on each call. Passing a function gives it a monotonically increasing integer; passing a list cycles through the values in order. Those two forms cover email addresses and enumerated fields without a separate random generator.

Associations embed directly into factory definitions. Setting author: build(:user) inside an article_factory produces a user struct alongside every article. Derived factories reuse an existing output:

elixir
def featured_article_factory do
  struct!(
    article_factory(),
    %{
      featured: true,
    }
  )
end

That pattern keeps the variant definition in the factory module, so tests that need a featured article call build(:featured_article) rather than passing a flag at every call site. Adding a second field to the variant later only requires updating the factory, not any test file.

build, insert and params_for serve three different test scenarios

ExMachina exposes three call families, each suited to a different kind of test.

build(:comment) produces a struct that lives only in the process memory for that test. Associations defined on the factory are also built in memory. build_pair/2 returns a two-element list; build_list(3, :comment) returns three. A test that checks pure business logic without touching the database runs on build calls throughout.

insert(:comment) commits the record to the configured Ecto repo and returns a persisted result, including any fields set by database defaults. Associated records are committed too. This variant requires use ExMachina.Ecto in the factory module and is not available on the non-Ecto path.

params_for(:comment) returns a plain map with no Ecto-specific keys, which suits Phoenix controller tests that pass a map to an action. params_with_assocs/2 does the same but inserts the belongs_to associations first and sets the foreign key fields. string_params_for/2 and string_params_with_assocs/2 return maps with string keys, matching the format a Phoenix controller receives from a parsed HTTP request body.

All functions accept an optional map or keyword list of overrides merged into the factory output. A test that needs an admin user calls build(:user, role: "admin") rather than defining a separate factory for that variant.

Eager evaluation means build(:user) resolves before the pair is created

build/2 is a regular Elixir function call, so its arguments evaluate before the function receives them. That has a practical consequence specific to pair and list functions.

Calling insert_pair(:account, user: build(:user)) evaluates build(:user) once. Both accounts receive the same user struct with identical field values. For a test that checks which user owns which account, that setup is wrong and the failure is not obvious.

Wrapping the association in an anonymous function delays execution:

elixir
insert_pair(:account, user: fn -> build(:user) end)

ExMachina evaluates that function once per record it creates, so each account receives a distinct user. The same delayed form works inside a factory definition:

elixir
def account_factory do
  %{user: fn -> build(:user) end}
end

The function can also accept the parent record as an argument. That form gives access to the struct's built fields, which is useful when an association's value should depend on a sibling field. The README notes that the parent record is only the struct after it is built, not after it is inserted. A database-assigned id is not yet present at that point, so factory logic that reads an id from the parent will get nil.

Passing one argument to a factory turns off automatic merging

ExMachina's default factory function takes no arguments and returns a struct, and any overrides passed at the call site are merged in automatically. That default breaks when the factory needs to compute fields from the caller's overrides before constructing the struct. In that case, the factory can accept one argument:

elixir
def custom_article_factory(attrs) do
  title = Map.get(attrs, :title, "default title")

  article = %Article{
    author: "John Doe",
    title: title
  }

  article
  |> merge_attributes(attrs)
  |> evaluate_lazy_attributes()
end

When a factory accepts attrs, ExMachina skips automatic merging and skips lazy attribute evaluation. Both steps become the developer's responsibility. Calling merge_attributes/2 and evaluate_lazy_attributes/1 at the end replicates the default behaviour for the fields that need it.

This form also enables non-map factories. Because the developer controls the return value entirely, the factory can return a list, a tuple or any other term the test needs. The README describes this as full control of factory and notes that omitting evaluate_lazy_attributes/1 is intentional when the factory should not evaluate anonymous function values.

v2.8.2 on Hex, MIT licence, and the parts of the test surface ex_machina does not cover

The current version is v2.8.2, published on 2026-08-02. The two preceding releases were v2.8.1 on 2026-07-14 and v2.8.0 on 2024-06-25. The last push to the repository was on 2026-09-15. The changelog is in CHANGELOG.md at the repository root. The licence is MIT, per the repository metadata and the LICENSE file at the top level.

Several functions work only with Ecto. insert, insert_pair, insert_list, params_for, params_with_assocs, string_params_for and string_params_with_assocs all require the factory to call use ExMachina.Ecto. A factory using bare use ExMachina has access to the build family only. That boundary matters when testing a layer that does not go through Ecto, such as a domain module or a service using a different persistence mechanism.

The library does not generate random data on its own. Field values come from the literal defaults in the factory definition and from the sequence/2 helper. For properties that require statistically varied values across many records, such as an age or a geographic coordinate, the factory definition has to produce those values itself or call a separate library.

The README note at the top warns that the main branch may not match the currently published version and links to the hexdocs for the latest stable release. That note is worth reading before copying an example directly from the repository.

Editorial conclusion

ExMachina suits Elixir applications that use Ecto and have grown past a handful of tests. The factory pattern keeps test setup out of test bodies and isolates model field changes to a single file. Skip it when your application has no persistence layer, since insert and params_for are Ecto-only and the remaining build variants give only half the value. Before adopting it, confirm that Application.ensure_all_started(:ex_machina) runs before ExUnit.start in test_helper.exs, and read the lazy evaluation section in hexdocs before writing any association-heavy factories.

Frequently asked questions

What is ExMachina?

ExMachina is an Elixir library that creates test data through factory modules. It integrates with Ecto for inserting records and their associations, and it can work without Ecto using the base ExMachina module, which provides the build family of functions.

How do I add ExMachina to a Phoenix project?

Add {:ex_machina, "~> 2.8.2", only: :test} to the deps function in mix.exs, then call Application.ensure_all_started(:ex_machina) before ExUnit.start in test/test_helper.exs. Factory modules go in test/support, which Phoenix includes in the compilation path by default.

What is the difference between build and insert in ExMachina?

build returns an unsaved struct built in memory with no database call. insert writes the record and its associations to the configured Ecto repo and returns the persisted result. insert is only available when the factory uses use ExMachina.Ecto.

Does ExMachina work without Ecto?

Yes. A factory using use ExMachina rather than use ExMachina.Ecto has access to the build family of functions. The insert, params_for and params_with_assocs functions are not available on that path, since they depend on an Ecto repo.

What does the sequence helper do in ExMachina?

The sequence/2 helper returns a different value on each call. Passing a function gives it a monotonically increasing integer; passing a list cycles through the values in order. It is commonly used for fields like email addresses that must be unique across factory calls.

Official sources

  1. beam-community/ex_machina on GitHub
  2. License: MIT
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/beam-community-ex-machina.svg)](https://hysenlabs.com/projects/beam-community-ex-machina)