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
endGenerated Functions
For a factory named :user with struct: User and a params argument that defaults to an empty
map:
| Function | Returns | Purpose |
|---|---|---|
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 configuration | Value builders | Params builders | Struct builders | Inserts |
|---|---|---|---|---|
No struct: option | Yes | No | No | No |
| Plain struct | No | Yes | Yes | With an insert: capture |
| Table-backed Ecto schema | No | Yes | Yes | Yes, unless insert: false |
| Embedded schema | No | Yes | Yes | With 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| Key | Removes |
|---|---|
:params | build_*_params/0,1,2 and its list |
:string_params | build_*_string_params/0,1,2 and its list |
:struct_list | build_*_struct_list/1,2,3 |
:params_list | build_*_params_list/1,2,3 |
:string_params_list | build_*_string_params_list/1,2,3 |
:insert_list | insert_*_list/1,2,3, including insert_via: target lists |
:non_struct_list | A non-struct factory's build_*_list/1,2,3 |
:paramsand:string_paramsare independent:build_*_string_paramsdoes not usebuild_*_params, so either can be disabled alone.- Everything else is always generated.
build_*_structcannot be disabled, since every other family builds on it, and inserts are switched off withinsert:andinsert_via:. disable:cascades per key, and variants follow their base factory.falseon a family that is not disabled has no effect.- A key for the other kind of factory (
:non_struct_liston 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)
endThe 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)
endA 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 supplies | Singular association | Plural association |
|---|---|---|
| Key absent | Treated as default: (%{} unless set) | Treated as default: ([] unless set) |
nil | nil | Raise; use [] |
| Params map | Built by the builder | Raise |
| Struct | Reused unchanged; the builder is not called | Raise |
| List of maps/structs | Raise | Each item resolved in order |
- Keys must be direct associations; embeds and
:throughassociations are not supported. Supplied structs and builder results must be the related schema (a singular builder may returnnil, unless the key isrequired: true). default: valueis what an absent key is treated as:nilor 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 insidedefault:raises.required: true(singular keys only, not withdefault: nil) raises when the key resolves tonil, from the caller, abefore_build_paramshook, or the builder. It does not check the finished struct.- Keys resolve top to bottom. A 2-arity builder's
factory_paramsholds 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(ordefault: []), or pass a value at each level. See Avoid recursive defaults. assocs:is evaluated at build time, like the body, and the factory'sparamsvariable is not in scope there. The declaration is validated when the factory first builds.assoc/3,4andassoc_list/3,4apply the same rules to one key, for helper functions and values computed in the body (withoutrequired: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:insertand: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'sinsert!/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:andinsert_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 withname: false(removing a target that is not inherited raises). The nameectois reserved.insert: falseremoves 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 forinsert_via:targets. A target that needs more steps does them in its own function. - Captures and targets do not run the
:ectoinsert's already-inserted check (see Hook Pipeline). - A target receives the struct with any built associations still in place. The
:ectoinsert cascades them; a capture or target stores them only if its function does. Anafter_build_structhook 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:
- Unknown keys, when building starts (before hooks and the body): every key must be a key
of the
:structoption's struct, which includes virtual fields and association keys. - Ignored or changed params, after the body and its lazy values (before
after_build_params): every field in the params that entered the body (afterbefore_build_paramsandassocs:) 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)
endSet 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, andinsert_*_struct).insert_via:targets run no hooks. - With
body: :struct, the params-stage hooks (before_build_params,after_build_params, andbefore_build_struct) are skipped;assocs:are resolved after params validation, and the returned struct is lazily evaluated. - For non-struct factories,
build_*runsbefore_build_params, the body with lazy evaluation, thenafter_build_params. insert_user_struct/1,2runs 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 rawRepo.insert!/2skips the insert hooks. With the:ectoinsert, 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
| Hook | Receives | Returns | When to Use |
|---|---|---|---|
:before_build_params | params (map) | params (map) | Transform or inject params before the factory body runs |
:after_build_params | params (map) | params (map) | Modify params after the factory body (e.g. add computed fields) |
:before_build_struct | params (map) | params (map) | Last chance to modify params before struct!() is called |
:after_build_struct | struct | struct | Transform the struct after creation (e.g. set virtual fields) |
:before_insert | struct | struct | Modify the struct just before it is inserted |
:after_insert | struct | struct | Post-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:
- Parent module -
use FactoryMan, hooks: [...] - Child module -
use FactoryMan, extends: Parent, hooks: [...](any number of levels) - 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}:
| Placement | Runs | Default for |
|---|---|---|
:after_parent | After the inherited hooks | before_* hooks |
:before_parent | Before the inherited hooks | after_* hooks |
:replace_parent | Instead 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
endVariant 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: :modrenames them (build_mod_struct); the variant is still listed as:admininvariants:andextends:. 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 fromforce:.- A variant without a body needs
defaults:,force:,extends:, orassocs:, and its head takes no argument (adminoradmin()).defaults:andforce:with a do block raise. defaults:andforce: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 adefaults: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 formatfrom adding parentheses, add:factory_mantoimport_depsin.formatter.exs.import_depsneeds the dependency in the environmentmix formatruns in (only: [:dev, :test]); otherwise adddefvariant: 2to your ownlocals_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)
endA 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 valueFactoryMan.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)
endDirect 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()}
endCookbook
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]:optsshows the resolved options, including each hook name's list in run order, the resolvedinsert:andinsert_via:, and the disabled families (disable:).assocs:is code evaluated at build time, so it does not appear.:factorieslists every factory and variant in the module (variants under their full name), for runtime dispatch without building function names from strings.:variantslists a factory's variants by the names thatvariants:andextends: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
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 supplies | Result |
|---|---|
| key absent | treated as default: (%{} unless set), then resolved |
nil | nil |
| params map | built with build_fun |
| a struct | reused 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:nilor 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)))
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"}])
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'sinsert!/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 aninsert_*_via_<name>family:name: captureadds or replaces a target, andname: falseremoves an inherited one.:disable- Function families to switch off, asfamily: true(orfamily: falseto 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 schemastruct:. See Associations in the module documentation.:strict-trueraises on param keys that are not fields of the:structoption'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"}
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.
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 thedeffactoryoption. 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 schemastruct:. The base still enforces its ownrequired: true, so a variant can addrequired:to a key but cannot relax it: a variantdefault: nilon 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.
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)
endIf you want to customize the returned string you can use sequence/2.
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)
endExample with a list
deffactory user(params \\ %{}), struct: User do
Map.merge(%{name: FactoryMan.sequence(:name, ["Joe", "Mike", "Sarah"])}, params)
end
@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