Want to take your software engineering career to the next level? Join the mailing list for career tips & advice Click here


Create test data for Elixir applications

Subscribe to updates I use ex_machina

Statistics on ex_machina

Number of watchers on Github 948
Number of open issues 24
Average time to close an issue 28 days
Main language Elixir
Average time to merge a PR 3 days
Open pull requests 23+
Closed pull requests 23+
Last commit over 2 years ago
Repo Created almost 5 years ago
Repo Last Updated over 2 years ago
Size 246 KB
Homepage https://hex.pm/pa...
Organization / Authorthoughtbot
Page Updated
Do you use ex_machina? Leave a review!
View open issues (24)
View ex_machina activity
View on github
Fresh, new opensource launches 🚀🚀🚀
Software engineers: It's time to get promoted. Starting NOW! Subscribe to my mailing list and I will equip you with tools, tips and actionable advice to grow in your career.
Evaluating ex_machina for your project? Score Explanation
Commits Score (?)
Issues & PR Score (?)


Circle CI

ExMachina is part of the thoughtbot Elixir family of projects.

ExMachina makes it easy to create test data and associations. It works great with Ecto, but is configurable to work with any persistence library.

This README follows master, which may not be the currently published version. Here are the docs for the latest published version of ExMachina.


To install in all environments (useful for generating seed data in dev/prod):

In mix.exs, add the ExMachina dependency:

def deps do
  # Get the latest from hex.pm. Works with Ecto 2.0
    {:ex_machina, "~> 2.1"},

And start the ExMachina application. For most projects (such as Phoenix apps) this will mean adding :ex_machina to the list of applications in mix.exs. You can skip this step if you are using Elixir 1.4

def application do
  [mod: {MyApp, []},
   applications: [:ex_machina, :other_apps...]]

Install in just the test environment with Phoenix:

In mix.exs, add the ExMachina dependency:

def deps do
    {:ex_machina, "~> 2.1", only: :test},

Add your factory module inside test/support so that it is only compiled in the test environment.

Next, be sure to start the application in your test/test_helper.exs before ExUnit.start:

{:ok, _} = Application.ensure_all_started(:ex_machina)

Install in just the test environment for non-Phoenix projects:

You will follow the same instructions as above, but you will also need to add test/support to your compilation paths (elixirc_paths) if you have not done so already.

In mix.exs, add test/support to your elixirc_paths for just the test env.

def project do
  [app: ...,
   # Add this if it's not already in your project definition.
   elixirc_paths: elixirc_paths(Mix.env)]

# This makes sure your factory and any other modules in test/support are compiled
# when in the test environment.
defp elixirc_paths(:test), do: ["lib", "web", "test/support"]
defp elixirc_paths(_), do: ["lib", "web"]


Check out the docs for more details.

Define factories:

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

  # without Ecto
  use ExMachina

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

  def article_factory do
      title: "Use ExMachina!",
      # associations are inserted when you call `insert`
      author: build(:user),

  def comment_factory do
      text: "It's great!",
      article: build(:article),

Using factories (check out the docs for more details):

# `attrs` are automatically merged in for all build/insert functions.

# `build*` returns an unsaved comment.
# Associated records defined on the factory are built.
attrs = %{body: "A comment!"} # attrs is optional. Also accepts a keyword list.
build(:comment, attrs)
build_pair(:comment, attrs)
build_list(3, :comment, attrs)

# `insert*` returns an inserted comment. Only works with ExMachina.Ecto
# Associated records defined on the factory are inserted as well.
insert(:comment, attrs)
insert_pair(:comment, attrs)
insert_list(3, :comment, attrs)

# `params_for` returns a plain map without any Ecto specific attributes.
# This is only available when using `ExMachina.Ecto`.
params_for(:comment, attrs)

# `params_with_assocs` is the same as `params_for` but inserts all belongs_to
# associations and sets the foreign keys.
# This is only available when using `ExMachina.Ecto`.
params_with_assocs(:comment, attrs)

# Use `string_params_for` to generate maps with string keys. This can be useful
# for Phoenix controller tests.
string_params_for(:comment, attrs)
string_params_with_assocs(:comment, attrs)

Usage in a test

# Example of use in Phoenix with a factory that uses ExMachina.Ecto
defmodule MyApp.MyModuleTest do
  use MyApp.ConnCase
  # If using Phoenix, import this inside the using block in MyApp.ConnCase
  import MyApp.Factory

  test "shows comments for an article" do
    conn = conn()
    article = insert(:article)
    comment = insert(:comment, article: article)

    conn = get conn, article_path(conn, :show, article.id)

    assert html_response(conn, 200) =~ article.title
    assert html_response(conn, 200) =~ comment.body

Where to put your factories

If you are using ExMachina in all environments:

Start by creating one factory module (such as MyApp.Factory) in lib/my_app/factory.ex and putting all factory definitions in that module.

If you are using ExMachina in only the test environment:

Start by creating one factory module (such as MyApp.Factory) in test/support/factory.ex and putting all factory definitions in that module.

Later on you can easily create different factories by creating a new module in the same directory. This can be helpful if you need to create factories that are used for different repos, your factory module is getting too big, or if you have different ways of saving the record for different types of factories.

Splitting factories into separate files

This example shows how to set up factories for the testing environment. For setting them in all environments, please see the To install in all environments section

Start by creating main factory module in test/support/factory.ex and name it MyApp.Factory. The purpose of the main factory is to allow you to include only a single module in all tests.

# test/support/factory.ex
defmodule MyApp.Factory do
  use ExMachina.Ecto, repo: MyApp.Repo
  use MyApp.ArticleFactory

The main factory includes MyApp.ArticleFactory, so let's create it next. It might be useful to create a separate directory for factories, like test/factories. Here is how to create a factory:

# test/factories/article_factory.ex
defmodule MyApp.ArticleFactory do
  defmacro __using__(_opts) do
    quote do
      def article_factory do
          title: "My awesome article!",
          body: "Still working on it!"

This way you can split your giant factory file into many small files. But what about name conflicts? Use pattern matching to avoid them!

# test/factories/post_factory.ex
defmodule MyApp.PostFactory do
  defmacro __using__(_opts) do
    quote do
      def post_factory do
          body: "Example body"

      def with_comments(%MyApp.Post{} = post) do
        insert_pair(:comment, post: post)

# test/factories/video_factory.ex
defmodule MyApp.VideoFactory do
  defmacro __using__(_opts) do
    quote do
      def video_factory do
          url: "example_url"

      def with_comments(%MyApp.Video{} = video) do
        insert_pair(:comment, video: video)

Ecto Associations

ExMachina will automatically save any associations when you call any of the insert functions. This includes belongs_to and anything that is inserted by Ecto when using Repo.insert!, such as has_many, has_one, and embeds. Since we automatically save these records for you, we advise that factory definitions only use build/2 when declaring associations, like so:

def article_factory do
    title: "Use ExMachina!",
    # associations are inserted when you call `insert`
    comments: [build(:comment)],
    author: build(:user),

Using insert/2 in factory definitions may lead to performance issues and bugs, as records will be saved unnecessarily.

Flexible Factories with Pipes

def make_admin(user) do
  %{user | admin: true}

def with_article(user) do
  insert(:article, user: user)

build(:user) |> make_admin |> insert |> with_article

Using with Phoenix

If you want to keep the factories somewhere other than test/support, change this line in mix.exs:

# Add the folder to the end of the list. In this case we're adding `test/factories`.
defp elixirc_paths(:test), do: ["lib", "web", "test/support", "test/factories"]

Custom Strategies

You can use ExMachina without Ecto, by using just the build functions, or you can define one or more custom strategies to use in your factory. You can also use custom strategies with Ecto. Here's an example of a strategy for json encoding your factories. See the docs on ExMachina.Strategy for more info.

defmodule MyApp.JsonEncodeStrategy do
  use ExMachina.Strategy, function_name: :json_encode

  def handle_json_encode(record, _opts) do

defmodule MyApp.Factory do
  use ExMachina
  # Using this will add json_encode/2, json_encode_pair/2 and json_encode_list/3
  use MyApp.JsonEncodeStrategy

  def user_factory do
    %User{name: "John"}

# Will build and then return a JSON encoded version of the user.


Before opening a pull request, please open an issue first.

$ git clone https://github.com/thoughtbot/ex_machina.git
$ cd ex_machina
$ mix deps.get
$ mix test

Once you've made your additions and mix test passes, go ahead and open a PR!


ExMachina is Copyright 2015 thoughtbot. It is free software, and may be redistributed under the terms specified in the LICENSE file.

About thoughtbot


ExMachina is maintained and funded by thoughtbot, inc. The names and logos for thoughtbot are trademarks of thoughtbot, inc.

We love open source software, Elixir, and Phoenix. See our other Elixir projects, or hire our Elixir Phoenix development team to design, develop, and grow your product.


ex_machina open issues Ask a question     (View All Issues)
  • over 3 years params_for returns updated_at and inserted_at
  • over 3 years has many associations not loaded with params_with_assocs
  • about 4 years Lazy attributes?
  • over 4 years fields_for seems to strip has_many through associations
ex_machina open pull requests (View All Pulls)
  • Nested association do not persist belongs_to
  • Allow to use custom changeset
  • Add insert, insert_pair and insert_list to ExMachina.Ecto
  • Fix undefined struct error
  • Add params_with_assocs function
  • Add support for lazy attributes
  • Drop fields that has nil values
  • Change `params_for` to return stringify keys
  • Fix most annoying elixir 1.4 warnings
  • Replaced usages of the deprecated HashDict module with Map in sequence.ex
  • Add `string_params_for`
  • Don't delete `has_many`s from param functions
  • Added tutorial for splitting factories into multiple files
  • Ecto: Support bare maps as values for embeds. Support lists of bare maps
  • Minor clean up of EctoStrategy
  • Clarify skipping application step of setup
  • Handle nil values for has_one associations
  • README: Show example of a derived attribute
  • Format code with Elixir 1.6 formatter
  • Expose EctoStrategy.cast/1
  • README: Add example of dependent factory
  • Add option to import factories
  • Fix user has many posts association
ex_machina list of languages used
Other projects in Elixir