{"id":13491463,"url":"https://github.com/beam-community/ex_machina","last_synced_at":"2025-05-14T22:06:08.063Z","repository":{"id":1370888,"uuid":"42458047","full_name":"beam-community/ex_machina","owner":"beam-community","description":"Create test data for Elixir applications","archived":false,"fork":false,"pushed_at":"2025-04-21T23:36:41.000Z","size":466,"stargazers_count":1988,"open_issues_count":0,"forks_count":147,"subscribers_count":29,"default_branch":"main","last_synced_at":"2025-05-07T21:58:15.259Z","etag":null,"topics":["elixir","factories","factory-definitions","testing"],"latest_commit_sha":null,"homepage":"https://hex.pm/packages/ex_machina","language":"Elixir","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/beam-community.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2015-09-14T15:40:28.000Z","updated_at":"2025-05-04T14:49:17.000Z","dependencies_parsed_at":"2023-10-03T15:38:22.200Z","dependency_job_id":"83943b50-d9cd-4458-92c4-d0661a0e4476","html_url":"https://github.com/beam-community/ex_machina","commit_stats":{"total_commits":290,"total_committers":70,"mean_commits":4.142857142857143,"dds":0.8310344827586207,"last_synced_commit":"dbe10678c7fcc898408ef801356c6cba4f7c002a"},"previous_names":["thoughtbot/ex_machina"],"tags_count":22,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/beam-community%2Fex_machina","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/beam-community%2Fex_machina/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/beam-community%2Fex_machina/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/beam-community%2Fex_machina/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/beam-community","download_url":"https://codeload.github.com/beam-community/ex_machina/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254049294,"owners_count":22006034,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["elixir","factories","factory-definitions","testing"],"created_at":"2024-07-31T19:00:57.261Z","updated_at":"2025-05-14T22:06:03.041Z","avatar_url":"https://github.com/beam-community.png","language":"Elixir","funding_links":[],"categories":["Elixir"],"sub_categories":[],"readme":"# ExMachina\n\n[![Continuous Integration](https://github.com/beam-community/ex_machina/actions/workflows/ci.yaml/badge.svg)](https://github.com/beam-community/ex_machina/actions/workflows/ci.yaml)\n[![Module Version](https://img.shields.io/hexpm/v/ex_machina.svg)](https://hex.pm/packages/ex_machina)\n[![Hex Docs](https://img.shields.io/badge/hex-docs-lightgreen.svg)](https://hexdocs.pm/ex_machina/)\n[![Total Download](https://img.shields.io/hexpm/dt/ex_machina.svg)](https://hex.pm/packages/ex_machina)\n[![License](https://img.shields.io/hexpm/l/ex_machina.svg)](https://github.com/beam-community/ex_machina/blob/master/LICENSE)\n[![Last Updated](https://img.shields.io/github/last-commit/beam-community/ex_machina.svg)](https://github.com/beam-community/ex_machina/commits/master)\n\nExMachina makes it easy to create test data and associations. It works great\nwith Ecto, but is configurable to work with any persistence library.\n\n\u003e **This README follows the main branch, which may not be the currently published version**. Here are the\n[docs for the latest published version of ExMachina](https://hexdocs.pm/ex_machina/readme.html).\n\n## Installation\n\nIn `mix.exs`, add the ExMachina dependency:\n\n\u003c!-- {x-release-please-start-version} --\u003e\n```elixir\ndef deps do\n  [\n    {:ex_machina, \"~\u003e 2.8.0\", only: :test},\n  ]\nend\n```\n\u003c!-- {x-release-please-end} --\u003e\n\nAdd your factory module inside `test/support` so that it is only compiled in the\ntest environment.\n\nNext, be sure to start the application in your `test/test_helper.exs` before\nExUnit.start:\n\n```elixir\n{:ok, _} = Application.ensure_all_started(:ex_machina)\n```\n\n#### Install in just the test environment for non-Phoenix projects\n\nYou will follow the same instructions as above, but you will also need to add\n`test/support` to your compilation paths (elixirc_paths) if you have not done\nso already.\n\nIn `mix.exs`, add test/support to your elixirc_paths for just the test env.\n\n```elixir\ndef project do\n  [app: ...,\n   # Add this if it's not already in your project definition.\n   elixirc_paths: elixirc_paths(Mix.env)]\nend\n\n# This makes sure your factory and any other modules in test/support are compiled\n# when in the test environment.\ndefp elixirc_paths(:test), do: [\"lib\", \"test/support\"]\ndefp elixirc_paths(_), do: [\"lib\"]\n```\n\n## Overview\n\n[Check out the docs](https://hexdocs.pm/ex_machina/ExMachina.html) for more details.\n\nDefine factories:\n\n```elixir\ndefmodule MyApp.Factory do\n  # with Ecto\n  use ExMachina.Ecto, repo: MyApp.Repo\n\n  # without Ecto\n  use ExMachina\n\n  def user_factory do\n    %MyApp.User{\n      name: \"Jane Smith\",\n      email: sequence(:email, \u0026\"email-#{\u00261}@example.com\"),\n      role: sequence(:role, [\"admin\", \"user\", \"other\"]),\n    }\n  end\n\n  def article_factory do\n    title = sequence(:title, \u0026\"Use ExMachina! (Part #{\u00261})\")\n    # derived attribute\n    slug = MyApp.Article.title_to_slug(title)\n    %MyApp.Article{\n      title: title,\n      slug: slug,\n      # another way to build derived attributes\n      tags: fn article -\u003e\n        if String.contains?(article.title, \"Silly\") do\n          [\"silly\"]\n        else\n          []\n        end\n      end,\n      # associations are inserted when you call `insert`\n      author: build(:user)\n    }\n  end\n\n  # derived factory\n  def featured_article_factory do\n    struct!(\n      article_factory(),\n      %{\n        featured: true,\n      }\n    )\n  end\n\n  def comment_factory do\n    %MyApp.Comment{\n      text: \"It's great!\",\n      article: build(:article),\n    }\n  end\nend\n```\n\nUsing factories ([check out the docs](https://hexdocs.pm/ex_machina/ExMachina.html) for more details):\n\n```elixir\n# `attrs` are automatically merged in for all build/insert functions.\n\n# `build*` returns an unsaved comment.\n# Associated records defined on the factory are built.\nattrs = %{body: \"A comment!\"} # attrs is optional. Also accepts a keyword list.\nbuild(:comment, attrs)\nbuild_pair(:comment, attrs)\nbuild_list(3, :comment, attrs)\n\n# `insert*` returns an inserted comment. Only works with ExMachina.Ecto\n# Associated records defined on the factory are inserted as well.\ninsert(:comment, attrs)\ninsert_pair(:comment, attrs)\ninsert_list(3, :comment, attrs)\n\n# `params_for` returns a plain map without any Ecto specific attributes.\n# This is only available when using `ExMachina.Ecto`.\nparams_for(:comment, attrs)\n\n# `params_with_assocs` is the same as `params_for` but inserts all belongs_to\n# associations and sets the foreign keys.\n# This is only available when using `ExMachina.Ecto`.\nparams_with_assocs(:comment, attrs)\n\n# Use `string_params_for` to generate maps with string keys. This can be useful\n# for Phoenix controller tests.\nstring_params_for(:comment, attrs)\nstring_params_with_assocs(:comment, attrs)\n```\n\n## Delayed evaluation of attributes\n\n`build/2` is a function call. As such, it gets evaluated immediately. So this\ncode:\n\n```elixir\ninsert_pair(:account, user: build(:user))\n```\n\nIs equivalent to this:\n\n```elixir\nuser = build(:user)\ninsert_pair(:account, user: user) # same user for both accounts\n```\n\nSometimes that presents a problem. Consider the following factory:\n\n```elixir\ndef user_factory do\n  %{name: \"Gandalf\", email: sequence(:email, \u0026\"gandalf#{\u00261}@istari.com\")}\nend\n```\n\nIf you want to build a separate `user` per `account`, then calling\n`insert_pair(:account, user: build(:user))` will not give you the desired\nresult.\n\nIn those cases, you can delay the execution of the factory by passing it as an\nanonymous function:\n\n```elixir\ninsert_pair(:account, user: fn -\u003e build(:user) end)\n```\n\nYou can also do that in a factory definition:\n\n```elixir\ndef account_factory do\n  %{user: fn -\u003e build(:user) end}\nend\n```\n\nYou can even accept the parent record as an argument to the function:\n\n```elixir\ndef account_factory do\n  %{user: fn account -\u003e build(:user, vip: account.premium) end}\nend\n```\n\nNote that the `account` passed to the anonymous function is only the struct\nafter it's built. It's not an inserted record. Thus, it does not have data that\nis only accessible after being inserted into the database (e.g. `id`).\n\n## Full control of factory\n\nBy default, ExMachina will merge the attributes you pass into build/insert into\nyour factory. But if you want full control of your attributes, you can define\nyour factory as accepting one argument, the attributes being passed into your\nfactory.\n\n```elixir\ndef custom_article_factory(attrs) do\n  title = Map.get(attrs, :title, \"default title\")\n\n  article = %Article{\n    author: \"John Doe\",\n    title: title\n  }\n\n  # merge attributes and evaluate lazy attributes at the end to emulate\n  # ExMachina's default behavior\n  article\n  |\u003e merge_attributes(attrs)\n  |\u003e evaluate_lazy_attributes()\nend\n```\n\n**NOTE** that in this case ExMachina will _not_ merge the attributes into your\nfactory, and it will not evaluate lazy attributes. You will have to do this on\nyour own if desired.\n\n### Non-map factories\n\nBecause you have full control of the factory when defining it with one argument,\nyou can build factories that are neither maps nor structs.\n\n```elixir\n# factory definition\ndef room_number_factory(attrs) do\n  %{floor: floor_number} = attrs\n  sequence(:room_number, \u0026\"#{floor_number}0#{\u00261}\")\nend\n\n# example usage\nbuild(:room_number, floor: 5)\n# =\u003e \"500\"\n\nbuild(:room_number, floor: 5)\n# =\u003e \"501\"\n```\n\n**NOTE** that you cannot use non-map factories with Ecto. So you cannot\n`insert(:room_number)`.\n\n## Usage in a test\n\n```elixir\n# Example of use in Phoenix with a factory that uses ExMachina.Ecto\ndefmodule MyApp.MyModuleTest do\n  use MyApp.ConnCase\n  # If using Phoenix, import this inside the using block in MyApp.ConnCase\n  import MyApp.Factory\n\n  test \"shows comments for an article\" do\n    conn = conn()\n    article = insert(:article)\n    comment = insert(:comment, article: article)\n\n    conn = get conn, article_path(conn, :show, article.id)\n\n    assert html_response(conn, 200) =~ article.title\n    assert html_response(conn, 200) =~ comment.body\n  end\nend\n```\n\n## Where to put your factories\n\nIf you are using ExMachina in all environments:\n\n\u003e Start by creating one factory module (such as `MyApp.Factory`) in\n`lib/my_app/factory.ex` and putting all factory definitions in that module.\n\nIf you are using ExMachina in only the test environment:\n\n\u003e Start by creating one factory module (such as `MyApp.Factory`) in\n`test/support/factory.ex` and putting all factory definitions in that module.\n\nLater on you can easily create different factories by creating a new module in\nthe same directory. This can be helpful if you need to create factories that are\nused for different repos, your factory module is getting too big, or if you have\ndifferent ways of saving the record for different types of factories.\n\n### Splitting factories into separate files\n\nThis 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\n\n\u003e 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.\n\n```elixir\n# test/support/factory.ex\ndefmodule MyApp.Factory do\n  use ExMachina.Ecto, repo: MyApp.Repo\n  use MyApp.ArticleFactory\nend\n```\n\nThe 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:\n\n```elixir\n# test/factories/article_factory.ex\ndefmodule MyApp.ArticleFactory do\n  defmacro __using__(_opts) do\n    quote do\n      def article_factory do\n        %MyApp.Article{\n          title: \"My awesome article!\",\n          body: \"Still working on it!\"\n        }\n      end\n    end\n  end\nend\n```\n\nThis way you can split your giant factory file into many small files. But what about name conflicts? Use pattern matching to avoid them!\n\n```elixir\n# test/factories/post_factory.ex\ndefmodule MyApp.PostFactory do\n  defmacro __using__(_opts) do\n    quote do\n      def post_factory do\n        %MyApp.Post{\n          body: \"Example body\"\n        }\n      end\n\n      def with_comments(%MyApp.Post{} = post) do\n        insert_pair(:comment, post: post)\n        post\n      end\n    end\n  end\nend\n\n# test/factories/video_factory.ex\ndefmodule MyApp.VideoFactory do\n  defmacro __using__(_opts) do\n    quote do\n      def video_factory do\n        %MyApp.Video{\n          url: \"example_url\"\n        }\n      end\n\n      def with_comments(%MyApp.Video{} = video) do\n        insert_pair(:comment, video: video)\n        video\n      end\n    end\n  end\nend\n```\n\nIf you place your factories outside of `test/support` make sure they will compile by adding that directory to the compilation paths in your `mix.exs` file. For example for the `test/factories` files above you would modify your file like so:\n\n```elixir\n# ./mix.exs\n...\n  defp elixirc_paths(:test), do: [\"lib\", \"test/factories\", \"test/support\"]\n...\n```\n\n## Ecto\n\n### Ecto Associations\n\nExMachina will automatically save any associations when you call any of the\n`insert` functions. This includes `belongs_to` and anything that is\ninserted by Ecto when using `Repo.insert!`, such as `has_many`, `has_one`,\nand embeds. Since we automatically save these records for you, we advise that\nfactory definitions only use `build/2` when declaring associations, like so:\n\n```elixir\ndef article_factory do\n  %Article{\n    title: \"Use ExMachina!\",\n    # associations are inserted when you call `insert`\n    comments: [build(:comment)],\n    author: build(:user),\n  }\nend\n```\n\nUsing `insert/2` in factory definitions may lead to performance issues and bugs,\nas records will be saved unnecessarily.\n\n### Passing options to Repo.insert!/2\n\n`ExMachina.Ecto` uses\n[`Repo.insert!/2`](https://hexdocs.pm/ecto/Ecto.Repo.html#c:insert!/2) to\ninsert records into the database. Sometimes you may want to pass options to deal\nwith multi-tenancy or return some values generated by the database. In those\ncases, you can use `c:ExMachina.Ecto.insert/3`:\n\nFor example,\n\n```elixir\n# return values from the database\ninsert(:user, [name: \"Jane\"], returning: true)\n\n# use a different prefix\ninsert(:user, [name: \"Jane\"], prefix: \"other_tenant\")\n```\n\n## Flexible Factories with Pipes\n\n```elixir\ndef make_admin(user) do\n  %{user | admin: true}\nend\n\ndef with_article(user) do\n  insert(:article, user: user)\n  user\nend\n\nbuild(:user) |\u003e make_admin |\u003e insert |\u003e with_article\n```\n\n## Using with Phoenix\n\nIf you want to keep the factories somewhere other than `test/support`,\nchange this line in `mix.exs`:\n\n```elixir\n# Add the folder to the end of the list. In this case we're adding `test/factories`.\ndefp elixirc_paths(:test), do: [\"lib\", \"test/support\", \"test/factories\"]\n```\n\n## Custom Strategies\n\nYou can use ExMachina without Ecto, by using just the `build` functions, or you\ncan define one or more custom strategies to use in your factory. You can also\nuse custom strategies with Ecto. Here's an example of a strategy for json\nencoding your factories. See the docs on [ExMachina.Strategy] for more info.\n\n[ExMachina.Strategy]: https://hexdocs.pm/ex_machina/ExMachina.Strategy.html\n\n```elixir\ndefmodule MyApp.JsonEncodeStrategy do\n  use ExMachina.Strategy, function_name: :json_encode\n\n  def handle_json_encode(record, _opts) do\n    Poison.encode!(record)\n  end\nend\n\ndefmodule MyApp.Factory do\n  use ExMachina\n  # Using this will add json_encode/2, json_encode_pair/2 and json_encode_list/3\n  use MyApp.JsonEncodeStrategy\n\n  def user_factory do\n    %User{name: \"John\"}\n  end\nend\n\n# Will build and then return a JSON encoded version of the user.\nMyApp.Factory.json_encode(:user)\n```\n\n## Contributing\n\nBefore opening a pull request, please open an issue first.\n\n    git clone https://github.com/thoughtbot/ex_machina.git\n    cd ex_machina\n    mix deps.get\n    mix test\n\nOnce you've made your additions and `mix test` passes, go ahead and open a PR!\n\n## License\n\nExMachina is Copyright © 2015 thoughtbot. It is free software, and may be\nredistributed under the terms specified in the [LICENSE](/LICENSE) file.\n\n## About thoughtbot\n\n![thoughtbot](https://thoughtbot.com/logo.png)\n\nExMachina is maintained and funded by thoughtbot, inc.\nThe names and logos for thoughtbot are trademarks of thoughtbot, inc.\n\nWe love open source software, Elixir, and Phoenix. See [our other Elixir\nprojects][elixir-phoenix], or [hire our Elixir Phoenix development team][hire]\nto design, develop, and grow your product.\n\n[elixir-phoenix]: https://thoughtbot.com/services/elixir-phoenix?utm_source=github\n[hire]: https://thoughtbot.com?utm_source=github\n\n## Inspiration\n\n* [Fixtures for Ecto](https://blog.danielberkompas.com/elixir/2015/07/16/fixtures-for-ecto.html)\n* [Factory Bot](https://github.com/thoughtbot/factory_bot)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbeam-community%2Fex_machina","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbeam-community%2Fex_machina","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbeam-community%2Fex_machina/lists"}