FactoryMan (Factory Man v0.19.0)

Copy Markdown View Source

An Elixir library for generating test data. Define factories with deffactory, and FactoryMan generates functions for building params and structs, and inserting records.

Quick Start

defmodule MyApp.Factory do
  use FactoryMan, repo: MyApp.Repo

  alias MyApp.Users.User

  deffactory user(params \\ %{}), struct: User do
    base_params = %{username: FactoryMan.sequence("user")}

    Map.merge(base_params, params)
  end
end

Generated Functions

For a factory named :user with struct: User and a params argument that defaults to an empty map:

FunctionReturnsPurpose
build_user_struct/0,1,2%User{}Struct in memory (not persisted)
build_user_params/0,1,2%{}Clean params map derived from struct
build_user_string_params/0,1,2%{"" => ...}Same, with string keys
insert_user/0,1,2%User{}Built and inserted
build_user_struct_list/1,2,3[%User{}, ...]List of structs
build_user_params_list/1,2,3[%{}, ...]List of params maps
build_user_string_params_list/1,2,3[%{}, ...]List of string-keyed params maps
insert_user_list/1,2,3[%User{}, ...]List of inserted records
insert_user_struct/1,2%User{}Inserts an already-built struct

The insert functions use the default insert target, the repo's insert!/2, which requires a configured repo and a table-backed Ecto schema (see Insert Targets). Each list item is built independently. insert_user_struct has no list counterpart.

Every builder takes options after its params (build_user_struct/2, build_user_struct_list/3, and so on). The only builder option is variants: (see Variant Factories). Insert functions take one keyword list: FactoryMan uses :variants, and every other option is passed to the insert target unchanged. The *_struct insert forms take no variants:, since the struct is already built:

insert_user(%{username: "alice"}, variants: [:admin], returning: true, prefix: "tenant_a")
#                                 └─ FactoryMan ───┘  └─ Ecto (Repo.insert!/2) ───────┘

Zero-arity builders and inserts require a default argument in the factory head. For struct factories, count-only list functions pass %{} and are omitted when the head pattern-matches its argument. Non-struct count-only list functions require a default argument and use that default.

What gets generated depends on the factory configuration:

Factory configurationValue buildersParams buildersStruct buildersInserts
No struct: optionYesNoNoNo
Plain structNoYesYesWith an insert: capture
Table-backed Ecto schemaNoYesYesYes, unless insert: false
Embedded schemaNoYesYesWith an insert: capture

Value builders are build_<name> and build_<name>_list. Any struct factory also gets an insert_<name>_via_<target> family for each insert_via: target. body: :struct changes how the struct is built, not which functions are generated.

Disabling function families

disable: switches off function families that a test suite does not use. true disables a family, and false enables an inherited one again:

use FactoryMan, repo: MyApp.Repo, disable: [string_params: true, insert_list: true]

deffactory user(params \\ %{}), struct: User, disable: [string_params: false] do
  Map.merge(%{username: FactoryMan.sequence("user")}, params)
end
KeyRemoves
:paramsbuild_*_params/0,1,2 and its list
:string_paramsbuild_*_string_params/0,1,2 and its list
:struct_listbuild_*_struct_list/1,2,3
:params_listbuild_*_params_list/1,2,3
:string_params_listbuild_*_string_params_list/1,2,3
:insert_listinsert_*_list/1,2,3, including insert_via: target lists
:non_struct_listA non-struct factory's build_*_list/1,2,3
  • :params and :string_params are independent: build_*_string_params does not use build_*_params, so either can be disabled alone.
  • Everything else is always generated. build_*_struct cannot be disabled, since every other family builds on it, and inserts are switched off with insert: and insert_via:.
  • disable: cascades per key, and variants follow their base factory. false on a family that is not disabled has no effect.
  • A key for the other kind of factory (:non_struct_list on a struct factory, or any other key on a non-struct factory) raises when written on the factory itself, whatever its value, and is ignored when inherited.
  • A disabled function's name is free: the module can define a function with that name.

Defining Factories

deffactory defines a named factory with one argument and a body. With struct: and the default body: :params, the body returns a map of that struct's fields (a key that is not a field raises, naming it). With body: :struct, it returns the struct itself. Without struct:, the body can return any value, and the factory generates only build_<name> and build_<name>_list:

deffactory search_opts(overrides \\ []) do
  Keyword.merge([page: 1, per_page: 20], overrides)
end

The argument can have any name, and can pattern-match (%{username: _} = params). Merge the params yourself, last, so the caller's values win: FactoryMan does not merge them for you.

Associations

Declare association builders with assocs: (Ecto schema struct: only). Each declared key is resolved before the body, so the body always receives it resolved, and the final Map.merge(base_params, params) stays correct:

deffactory author(params \\ %{}), struct: Author, assocs: [user: &build_user_struct/1] do
  base_params = %{name: "Author of #{params.user.username}"}

  Map.merge(base_params, params)
end

A builder is a 1-arity function (a local or remote capture, or an anonymous function), a 2-arity function (params, factory_params), or {builder, options}. The builder is the default: an absent key is built with %{} (a list key with []), so a base_params default for a declared key is never used.

Caller suppliesSingular associationPlural association
Key absentTreated as default: (%{} unless set)Treated as default: ([] unless set)
nilnilRaise; use []
Params mapBuilt by the builderRaise
StructReused unchanged; the builder is not calledRaise
List of maps/structsRaiseEach item resolved in order
  • Keys must be direct associations; embeds and :through associations are not supported. Supplied structs and builder results must be the related schema (a singular builder may return nil, unless the key is required: true).
  • default: value is what an absent key is treated as: nil or a params map for a singular association, a list of params maps for a plural one. It is evaluated on every build, even when the caller supplies the key, so keep it a literal. A record at an association position inside default: raises.
  • required: true (singular keys only, not with default: nil) raises when the key resolves to nil, from the caller, a before_build_params hook, or the builder. It does not check the finished struct.
  • Keys resolve top to bottom. A 2-arity builder's factory_params holds the caller's params with the keys declared above it resolved; the current key and the keys below it are hidden, and undeclared keys are visible as given. See Chain associations.
  • A self-referential or mutually recursive default build raises instead of recursing forever. This includes recursion that would stop on its own while the key stays absent. Declare such a key with default: nil (or default: []), or pass a value at each level. See Avoid recursive defaults.
  • assocs: is evaluated at build time, like the body, and the factory's params variable is not in scope there. The declaration is validated when the factory first builds.
  • assoc/3,4 and assoc_list/3,4 apply the same rules to one key, for helper functions and values computed in the body (without required: or the schema check). They return the resolved value without changing the params, so put it back (or drop the key) before the final merge.

Params Functions

For struct factories, build_*_params and build_*_string_params build the struct and convert it to a clean map for changesets or controller tests. For Ecto schemas, Ecto metadata, autogenerated IDs, and unloaded associations are removed; plain structs are converted with Map.from_struct/1:

build_user_params(%{username: "alice"})         # %{username: "alice", first_name: nil, ...}
build_user_string_params(%{username: "alice"})  # %{"username" => "alice", ...}

Fields whose value is nil are kept, so a changeset test sees a nil field as given, not as absent; nil embeds are omitted. belongs_to associations are removed, and their foreign key is set when the associated record is persisted. Struct values such as DateTime are left untouched.

Factory Options

Options cascade: parent module -> child module -> individual factory.

Module-level (set with use FactoryMan): :repo, :extends (the parent factory module), :hooks, :insert, :insert_via, :disable, :body, :strict, and :struct.

Factory-level (set with deffactory):

  • :struct - The struct or Ecto schema to build (enables struct, params, and insert functions)
  • :body - What the body returns: :params (default) or :struct. Ignored for non-struct factories.
  • :assocs - Association builders (see Associations). Factory-level only.
  • :strict - true, false, or [allow: [...]] (see Strict Params). Ignored for non-struct factories.
  • :hooks - Chained with the module's hooks (see Hooks)
  • :repo - Overrides the module's repo
  • :insert and :insert_via - The default and named insert targets (see Insert Targets)
  • :disable - Function families to switch off (see Disabling function families)

Variant-level (set with defvariant): :for, :as, :extends, :assocs, and, for a variant without a body, :defaults and :force (see Variant Factories). Unknown options raise at every level.

Insert Targets

An insert target is the function that inserts a built struct. The default target, set with insert:, is used by insert_*, insert_*_list, and insert_*_struct:

  • :ecto (the default) - the repo's insert!/2. Generated for a table-backed Ecto schema when a repo is configured; otherwise nothing is generated. Written on a factory that cannot be inserted with Ecto, it raises.
  • false - no default insert functions.
  • A remote capture of arity 2, such as &MyApp.Factory.put!/2 - generated for any struct factory: plain structs, embedded schemas, and Ecto schemas. Anonymous functions and local captures raise.

insert_via: adds named targets, each with its own family of functions:

use FactoryMan, repo: MyApp.Repo, insert_via: [search: &MyApp.Factory.index!/2]

deffactory user(params \\ %{}), struct: User do
  Map.merge(%{username: FactoryMan.sequence("user")}, params)
end

insert_user()                        # MyApp.Repo.insert!/2, with the insert hooks
insert_user_via_search()             # MyApp.Factory.index!/2
insert_user_via_search_list(3)
insert_user_struct_via_search(user)

An insert function receives the built struct and the caller's options (without :variants), and returns the inserted struct. A store that takes no options ignores them: def index!(struct, _opts).

  • insert: and insert_via: cascade like other options. insert_via: merges by name: a lower level adds a target, replaces the inherited target of the same name, or removes it with name: false (removing a target that is not inherited raises). The name ecto is reserved.
  • insert: false removes the default family only; insert_via: targets remain. disable: [insert_list: true] removes the list functions of both.
  • The insert hooks (before_insert, after_insert) run for the default target, not for insert_via: targets. A target that needs more steps does them in its own function.
  • Captures and targets do not run the :ecto insert's already-inserted check (see Hook Pipeline).
  • A target receives the struct with any built associations still in place. The :ecto insert cascades them; a capture or target stores them only if its function does. An after_build_struct hook applies to every family.
  • Variants get their base factory's targets, building through the variant.
  • Both options apply to struct factories only: written on a non-struct factory they raise, and inherited ones are ignored.

The Cookbook has recipes for inserting into other stores.

Strict Params

strict: true catches params that a factory silently ignores. It runs two checks:

  1. Unknown keys, when building starts (before hooks and the body): every key must be a key of the :struct option's struct, which includes virtual fields and association keys.
  2. Ignored or changed params, after the body and its lazy values (before after_build_params): every field in the params that entered the body (after before_build_params and assocs:) must come out of the body with the same value, compared with ==. A plain map has been kept when each of its keys has been kept, including when the body builds a struct such as an embed from it. Function values and association keys are not checked.
deffactory user(params \\ %{}), struct: User, strict: true do
  %{username: "default"}
end

build_user_struct(%{username: "alice"})
# ** (ArgumentError) strict factory :user in MyApp.Factory ignored or changed params it was given:
#
#      :username - given "alice", built "default"
#    ...

Both checks cover every derived function. The second check runs in the base factory's body, so it sees what the variants pass on; a variant body that drops a caller's key is not checked.

strict: [allow: [...]] exempts keys from both checks: a key that is not a field may be passed (e.g. an input used only to derive other fields), and a field may be changed by the body. An allowed key that is not a field must not reach the struct, so the body removes it (here with Map.pop/3):

deffactory invoice(params \\ %{}), struct: Invoice, strict: [allow: [:line_item_count]] do
  {count, params} = Map.pop(params, :line_item_count, 1)

  Map.merge(%{total: count * 100}, params)
end

Set it once in a base factory (use FactoryMan, strict: true); a factory can override it. It is ignored for non-struct factories.

Hooks

Hooks transform data at specific stages. Every stage has a before and an after hook.

Hook Pipeline

build_user_struct:
  params validation → before_build_params → assocs: resolution
  → [factory body + lazy eval] → strict params check → after_build_params
  → before_build_struct → struct!() → after_build_struct

build_user_params (calls build_user_struct internally):
  → strip Ecto metadata (or Map.from_struct/1 for plain structs)

insert_user (calls build_user_struct internally):
  → before_insert → Repo.insert!() → after_insert
  • The insert hooks belong to the default insert target's functions (insert_*, insert_*_list, and insert_*_struct). insert_via: targets run no hooks.
  • With body: :struct, the params-stage hooks (before_build_params, after_build_params, and before_build_struct) are skipped; assocs: are resolved after params validation, and the returned struct is lazily evaluated.
  • For non-struct factories, build_* runs before_build_params, the body with lazy evaluation, then after_build_params.
  • insert_user_struct/1,2 runs the insert pipeline on an already-built struct (it does not resolve associations). Use it after modifying a built struct, so records are shaped the same way however they were built; a raw Repo.insert!/2 skips the insert hooks. With the :ecto insert, it raises on a struct that has already been inserted (or deleted). To insert a copy on purpose, mark it as built first: Ecto.put_meta(struct, state: :built).

Hook Reference

HookReceivesReturnsWhen to Use
:before_build_paramsparams (map)params (map)Transform or inject params before the factory body runs
:after_build_paramsparams (map)params (map)Modify params after the factory body (e.g. add computed fields)
:before_build_structparams (map)params (map)Last chance to modify params before struct!() is called
:after_build_structstructstructTransform the struct after creation (e.g. set virtual fields)
:before_insertstructstructModify the struct just before it is inserted
:after_insertstructstructPost-process after insertion (e.g. reset associations)

A hook is a 1-arity remote capture such as &__MODULE__.my_hook/1, or a list of them, run in order (after_insert: [&M.reset/1, &M.log/1]). Hooks are compiled into the generated functions, so anything else, such as an anonymous function, raises at compile time. See the Cookbook for recipes.

Hook Order

Hooks can be set at three levels, and a hook set at a lower level is chained with the hooks it inherits instead of replacing them:

  1. Parent module - use FactoryMan, hooks: [...]
  2. Child module - use FactoryMan, extends: Parent, hooks: [...] (any number of levels)
  3. Individual factory - deffactory name(params), hooks: [...]

The order is onion-style, like middleware: the parent wraps the child. For a before_* hook the parent's runs first, and for an after_* hook the parent's runs last.

To place a hook (or a list of hooks) explicitly, pass {hook, placement}:

PlacementRunsDefault for
:after_parentAfter the inherited hooksbefore_* hooks
:before_parentBefore the inherited hooksafter_* hooks
:replace_parentInstead of all of the inherited hooks-

"Parent" means everything inherited for that hook name, from every level above. Each level is resolved against the list built so far. For example, with after_insert:

# Parent module:       after_insert: &P.hook/1                      -> [P]
# Child module:        after_insert: {&C.hook/1, :after_parent}     -> [P, C]
# Factory:             after_insert: &F.hook/1                      -> [F, P, C]

Each hook receives the previous hook's result. The same hook set at two levels runs twice. On a module with no parent, a placement behaves like a plain hook. To switch off the inherited hooks for one factory, use hooks: [after_insert: {[], :replace_parent}]; an empty list with any other placement raises. Unknown hook names and a hook name set twice at one level raise.

Factory Inheritance

A child module inherits its parent's options (repo, hooks, insert targets, strict:, ...) and can call the parent's helper functions unqualified (here, a generate_username/0 defined in MyApp.Factory). Chains can be any length:

defmodule MyApp.Factory.Accounts do
  use FactoryMan, extends: MyApp.Factory

  deffactory user(params \\ %{}), struct: User do
    Map.merge(%{username: generate_username()}, params)
  end
end

Variant Factories (defvariant)

A variant is a preprocessor for a base factory: its body runs first, on the caller's params, and passes the result to the base factory:

defvariant admin(params \\ %{}), for: :user do
  Map.merge(%{role: "admin"}, params)
end

build_admin_user_struct()   # same as build_user_struct(%{role: "admin"})
  • Its functions are named {variant}_{base} (build_admin_user_struct, insert_admin_user, ...), with the base factory's full family. as: :mod renames them (build_mod_struct); the variant is still listed as :admin in variants: and extends:.
  • for: must name a factory. A variant is defined after its base factory, in the same module, and variant names are unique per factory.
  • Params last (Map.merge(%{role: "admin"}, params)) makes the variant's values defaults, which the caller can override. Params first (Map.merge(params, %{banned: true})) forces them: they win over the caller and over later variants. See Choose defaults or forced values.

Variants without a body

A variant that only sets values can declare them instead of writing a body:

defvariant admin, for: :user, defaults: %{role: "admin"}
defvariant banned, for: :user, force: %{banned: true}
defvariant banned_admin, for: :user, extends: [:admin, :banned]
  • defaults: is merged under the params (Map.merge(defaults, params)), so the caller wins. force: is merged over them (Map.merge(params, force)), so the variant wins. With both, a key in both is taken from force:.
  • A variant without a body needs defaults:, force:, extends:, or assocs:, and its head takes no argument (admin or admin()). defaults: and force: with a do block raise.
  • defaults: and force: are code, evaluated on every build like a body, so they can call functions (defaults: admin_defaults()); each must evaluate to a map. Lazy values in them reach the base factory as params and are resolved there.
  • With assocs:, the values are merged first and the associations resolved after, so a defaults: value for a declared key is what the variant's builder builds from.
  • Any base factory works, as long as its params are maps.
  • A variant without a body never drops a caller's key, so the base factory's strict check sees every key the caller passed.
  • To keep mix format from adding parentheses, add :factory_man to import_deps in .formatter.exs. import_deps needs the dependency in the environment mix format runs in (only: [:dev, :test]); otherwise add defvariant: 2 to your own locals_without_parens.

Combining variants with variants:

Every generated function of a factory takes a variants: option, which applies several variants in one build:

build_user_struct(%{}, variants: [:admin, :confirmed])
insert_user(%{}, variants: [:admin], returning: true)

List variants by their defvariant name (:admin, not :admin_user). For variants that merge params last, the caller's params win, then a later variant wins over an earlier one. The chain runs from the last variant to the base factory, so a later variant's values reach an earlier one as params. A variant's own functions count the variant as the first in the list: build_admin_user_struct(params, variants: [:confirmed]) is build_user_struct(params, variants: [:admin, :confirmed]). An unknown variant name raises and lists the known variants.

Building on other variants with extends:

A variant builds on other variants of the same factory with extends:, and wins over them:

defvariant senior(params \\ %{}), for: :user, extends: [:admin] do
  Map.merge(%{title: "Senior admin"}, params)
end

A listed variant brings the variants it extends, and each variant is applied once, at its first position in the list, so a variant wins over the variants it extends whatever the list order. A variant is defined after the variants it extends. A variant with only extends: and no body gives a recurring combination a name (see Variants without a body).

Sequences

FactoryMan.sequence("user")                                          # "user0", "user1", ...
FactoryMan.sequence(:email, fn n -> "user#{n}@example.com" end)     # custom formatter
FactoryMan.sequence(:role, ["admin", "moderator", "user"])           # cycles through list
FactoryMan.sequence(:order, fn n -> "ORD-#{n}" end, start_at: 1000) # custom start value

FactoryMan.Sequence.reset/0 clears every counter, for every test. Tests that run at the same time share the counters, so in async tests reset only the sequence names that test uses: FactoryMan.Sequence.reset(:invoice_number).

Lazy Evaluation

Functions in a body's map or keyword list are evaluated at build time. Other values are passed through unchanged:

%{
  created_at: fn -> DateTime.utc_now() end,               # 0-arity: called with no args
  display_name: fn user -> "#{user.username} (User)" end # 1-arity: receives the parent map
}

Lazy evaluation ordering

Lazy values are resolved in two passes: the 0-arity functions first, then the 1-arity ones. A 1-arity function therefore sees plain values and resolved 0-arity values, but not another 1-arity field, which is still a function reference when it runs.

Embedded Schemas

A factory for an embedded schema generates the struct and params builders. It has no table, so it gets insert functions only from an insert: capture or insert_via: targets:

deffactory settings(params \\ %{}), struct: MyApp.Users.Settings do
  Map.merge(%{theme: "dark", notifications: true}, params)
end

Direct Struct Factories (body: :struct)

With body: :struct, the body returns the struct itself, for factories that need full control over construction. The full function family is still generated, including build_*_params, and lazy values in the struct are resolved. It can be set at the module level and overridden per factory:

deffactory invoice(params \\ %{}), struct: Invoice, body: :struct do
  %Invoice{total: Map.get(params, :total, 100), customer: build_customer_struct()}
end

Cookbook

The Cookbook is a practical progression from a first factory through realistic defaults, variants, associations, suite organization, hooks, strict params, insert targets, and advanced presets.

Reflection and Debugging

Every factory module gets a __factory_man__/1,2 reflection function:

iex> MyApp.Factory.__factory_man__(:opts)
[disable: [], insert_via: [], insert: :ecto, repo: MyApp.Repo]

iex> MyApp.Factories.Users.__factory_man__(:opts, :user)
[disable: [], insert_via: [], insert: :ecto, repo: MyApp.Repo, struct: User]

iex> MyApp.Factories.Users.__factory_man__(:factories)
[:user, :admin_user]

iex> MyApp.Factories.Users.__factory_man__(:variants, :user)
[:admin]
  • :opts shows the resolved options, including each hook name's list in run order, the resolved insert: and insert_via:, and the disabled families (disable:). assocs: is code evaluated at build time, so it does not appear.
  • :factories lists every factory and variant in the module (variants under their full name), for runtime dispatch without building function names from strings.
  • :variants lists a factory's variants by the names that variants: and extends: accept.

Summary

Functions

Resolve one association from a params map.

Resolve a list association from a params map.

Defines a factory that generates test data.

Defines a variant factory without a body, from its options.

Defines a variant factory that wraps a base factory.

Generates a sequence of strings.

Returns the next value in a named sequence.

Returns the next value in a named sequence using a formatter function.

Functions

assoc(params, key, build_fun, opts \\ [])

@spec assoc(map(), atom(), (map() -> any()), keyword()) :: any()

Resolve one association from a params map.

Reads key from params and resolves it with the same rules as the assocs: option (there is no required: option here):

Caller suppliesResult
key absenttreated as default: (%{} unless set), then resolved
nilnil
params mapbuilt with build_fun
a structreused unchanged, build_fun not called

Use it where assocs: does not fit: an association resolved from a value computed in the body, a plain helper function, or inside a builder. params is not modified; put the result back, or drop the key, before a final Map.merge(base_params, params).

Unlike assocs:, the result is not checked against a schema. A default build that starts again inside itself raises, as it does for assocs: (see Recursion in the module documentation).

Options

  • :default - what an absent key is treated as: nil or a params map (default: %{}). It is evaluated on every call, even when the key is present, so keep it literal.

Examples

FactoryMan.assoc(params, :author, &build_user_struct/1)
FactoryMan.assoc(params, :editor, &build_user_struct/1, default: nil)
FactoryMan.assoc(params, :author, &build_user_struct(Map.merge(%{role: "writer"}, &1)))

assoc_list(params, key, build_fun, opts \\ [])

@spec assoc_list(map(), atom(), (map() -> any()), keyword()) :: list()

Resolve a list association from a params map.

Reads key from params. An absent key is treated as default: ([] unless set). An explicit nil raises (use [] for no associated values). Each list item must be a params map (built with build_fun, which must not return nil) or a struct (reused unchanged); order is preserved.

Like assoc/3,4, results are not checked against a schema, and a default build that starts again inside itself raises.

Options

  • :default - what an absent key is treated as: a list of params maps (default: []). It is evaluated on every call, even when the key is present, so keep it literal.

Examples

FactoryMan.assoc_list(params, :tags, &build_tag_struct/1)
FactoryMan.assoc_list(params, :tags, &build_tag_struct/1, default: [%{name: "elixir"}])

deffactory(factory_head, opts \\ [], list)

(macro)

Defines a factory that generates test data.

The deffactory macro creates a set of functions for building test data. It works like defining a function, where you specify the factory name and a parameter (typically params).

Options

  • :struct - The struct or Ecto schema module to build. When provided, generates struct, params, and insert functions.
  • :insert - The default insert target: :ecto (default, the repo's insert!/2), false (no default insert functions), or a remote capture of arity 2 such as &MyApp.Factory.put!/2. Overrides the module-level value.
  • :insert_via - Named insert targets, each generating an insert_*_via_<name> family: name: capture adds or replaces a target, and name: false removes an inherited one.
  • :disable - Function families to switch off, as family: true (or family: false to enable an inherited one again). See Disabling function families in the module documentation.
  • :body - What the factory body returns: :params (default, a params map) or :struct (a struct built directly by the body). Params functions are generated either way (derived from the struct), and lazy values are resolved either way. Ignored for non-struct factories.
  • :hooks - A keyword list of hook functions to apply at different stages, chained with the module's hooks (see Hooks)
  • :repo - Overrides the module-level repo for this factory
  • :assocs - A keyword list mapping Ecto association keys to builders, resolved before the body. Requires an Ecto schema struct:. See Associations in the module documentation.
  • :strict - true raises on param keys that are not fields of the :struct option's struct, and on params the body ignores or changes. [allow: [...]] does the same, except for the listed keys (see Strict Params). Ignored for non-struct factories.

Generated Functions

A struct factory generates the build_*_struct, build_*_params, build_*_string_params, and insert families, and a factory without struct: generates build_* and build_*_list. See Generated Functions in the module documentation for the full list and the rules that decide which are generated.

Examples

deffactory user(params \\ %{}), struct: User do
  base_params = %{
    username: FactoryMan.sequence("user"),
    email: FactoryMan.sequence(:email, fn n -> "user#{n}@example.com" end)
  }

  Map.merge(base_params, params)
end

iex> MyApp.Factory.build_user_params(%{username: "alice"})
%{username: "alice", email: "user0@example.com", role: nil, ...}

iex> MyApp.Factory.insert_user(%{role: "admin"})
%User{id: 1, username: "user1", email: "user1@example.com", role: "admin"}

defvariant(variant_head, opts)

(macro)

Defines a variant factory without a body, from its options.

defvariant admin, for: :user, defaults: %{role: "admin"}
defvariant banned, for: :user, force: %{banned: true}
defvariant banned_admin, for: :user, extends: [:admin, :banned]

Takes the options of defvariant/3, plus:

  • :defaults - A map merged under the caller's params, so the caller wins.
  • :force - A map merged over the caller's params, so the variant wins.

Both are evaluated on every build. At least one of :defaults, :force, :extends, and :assocs is required. See Variants without a body in the module documentation.

defvariant(variant_head, opts, list)

(macro)

Defines a variant factory that wraps a base factory.

A variant is a preprocessor: it receives the caller's params, transforms them, and delegates to the base factory. The variant body runs before the base factory, not after.

Example

deffactory user(params \\ %{}), struct: User do
  base_params = %{username: FactoryMan.sequence("user"), role: "member"}

  Map.merge(base_params, params)
end

defvariant admin(params \\ %{}), for: :user do
  base_params = %{role: "admin"}

  Map.merge(base_params, params)
end

# Generated: build_admin_user_struct/0,1,2, insert_admin_user/0,1,2, etc.
# Calling build_admin_user_struct() is equivalent to:
#   build_user_struct(%{role: "admin"})

Options

  • :for - (atom, required) The name of the base factory to wrap (e.g. :user). It must name a factory; a variant builds on other variants with :extends.

  • :extends - A list of variants of the same factory to build on (e.g. extends: [:admin]). They run before this variant's body, so this variant wins over them. Each must be defined earlier in the module.

  • :as - Instead of the default <variant>_<base> structure used when generating factory functions, (e.g. build_admin_user_struct), you may specify a custom name to use when generating the factory functions (e.g. as: :admin -> build_admin_struct)

  • :assocs - Association builders resolved before the variant body, with the same shape as the deffactory option. The base factory reuses the resolved structs, so a variant can build an association differently from its base. Requires a base factory with an Ecto schema struct:. The base still enforces its own required: true, so a variant can add required: to a key but cannot relax it: a variant default: nil on a key the base requires raises on every default build.

Variants are listed by their name (e.g. :admin) in extends: and in the variants: option of the generated functions (see Variant Factories in the module documentation).

For a variant without a body, see defvariant/2.

sequence(name)

@spec sequence(String.t()) :: String.t()

Generates a sequence of strings.

The sequence name is used as the beginning of the string. For example, if you do FactoryMan.sequence("joe"), you will get back "joe0", then "joe1", and so on.

Example

deffactory user(params \\ %{}), struct: User do
  Map.merge(%{username: FactoryMan.sequence("joe")}, params)
end

If you want to customize the returned string you can use sequence/2.

sequence(name, formatter)

@spec sequence(any(), (integer() -> any()) | [...]) :: any()

Returns the next value in a named sequence.

A formatter function receives the current counter. A list cycles through its items. Returned values are not guaranteed to be unique.

Example with a formatter function

deffactory user(params \\ %{}), struct: User do
  Map.merge(%{email: FactoryMan.sequence(:email, fn n -> "me-#{n}@foo.com" end)}, params)
end

Example with a list

deffactory user(params \\ %{}), struct: User do
  Map.merge(%{name: FactoryMan.sequence(:name, ["Joe", "Mike", "Sarah"])}, params)
end

sequence(name, formatter, opts)

@spec sequence(any(), (integer() -> any()), [{:start_at, non_neg_integer()}]) :: any()

Returns the next value in a named sequence using a formatter function.

:start_at sets the initial counter when the named sequence does not yet exist. It does not change an existing counter. List formatters are supported by sequence/2, not sequence/3.

Example

deffactory price(params \\ %{}), struct: Price do
  Map.merge(%{cents: FactoryMan.sequence(:cents, fn n -> n end, start_at: 600)}, params)
end