FactoryMan (Factory Man v0.15.0)

View Source

An Elixir library for generating test data. Define factories with deffactory, and FactoryMan generates functions for building params and structs, and inserting database 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: "user-#{System.os_time()}"}

    Map.merge(base_params, params)
  end
end

Generated Functions

For a factory named :user with struct: User and a plain params argument that defaults to an empty map, the following functions are available. Insert functions require a configured repo, a table-backed Ecto schema, and insert? not set to false.

FunctionReturnsPurpose
build_user_struct/0,1%User{}Struct in memory (not persisted)
build_user_params/0,1%{}Clean params map derived from struct
build_user_string_params/0,1%{"" => ...}Same, with string keys
insert_user/0,1,2%User{}Inserted into database
build_user_struct_list/1,2[%User{}, ...]List of structs
build_user_params_list/1,2[%{}, ...]List of params maps
build_user_string_params_list/1,2[%{}, ...]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

Builders and insert_user accept factory params. insert_user_struct takes an existing %User{} instead. Insert functions also accept repo options. Each list item is built independently.

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 structNoYesYesNo
Table-backed Ecto schemaNoYesYesYes, when enabled
Embedded schemaNoYesYesNo

Value builders use build_<name> and build_<name>_list. Params builders include atom-keyed and string-keyed functions. Inserts require a configured repo and insert? not set to false. Setting body: :struct changes how the struct is built, not which function families are generated.

The diagram shows the build pipeline with body: :params. Declared assocs: are resolved after before_build_params and before the factory body. Each function shown has a list counterpart that builds every item independently. insert_user_struct, not shown, starts at the insert pipeline and has no list counterpart:

flowchart TD
    body["factory body<br/>(returns params map)"]
    struct_fn["build_user_struct/0,1<br/>params hooks → lazy eval → struct!/2 → struct hooks"]
    params_fn["build_user_params/0,1<br/>struct stripped to a clean map"]
    string_params_fn["build_user_string_params/0,1<br/>keys converted to strings"]
    insert_fn["insert_user/0,1,2<br/>insert hooks → Repo.insert!/2"]

    body --> struct_fn
    struct_fn --> params_fn
    params_fn --> string_params_fn
    struct_fn --> insert_fn

Defining Factories

The deffactory macro defines a named factory with one argument and a body. With struct: and the default body: :params, return a plain map containing only fields of that struct. With body: :struct, return the struct directly. Without struct:, the body can return any value.

deffactory user(params \\ %{}), struct: User do
  base_params = %{username: "user-#{System.os_time()}"}

  Map.merge(base_params, params)
end

You can name the parameter anything, and use pattern matching:

deffactory user_from_config(%{username: username} = params), struct: User do
  base_params = %{username: username}

  Map.merge(base_params, params)
end

Struct vs. Non-Struct Factories

The struct: option controls both what functions are generated and how they're named:

# Struct factory: generates build_user_params, build_user_struct, insert_user, etc.
deffactory user(params \\ %{}), struct: User do
  base_params = %{username: "user-#{System.os_time()}"}
  Map.merge(base_params, params)
end

# Non-struct factory: generates build_api_payload and build_api_payload_list only
deffactory api_payload(params \\ %{}) do
  %{action: "create", data: params}
end
Factory typeGenerated functions
struct: User (:user)build_user_params, build_user_struct, insert_user, etc.
No struct: (:api_payload)build_api_payload, build_api_payload_list

Non-struct factories use simplified names (build_* instead of build_*_params) because they can return any value: maps, strings, keyword lists, tuples, nil, etc.:

deffactory greeting(name \\ "world") do
  "Hello, #{name}!"
end

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

Lazy evaluation works in keyword lists the same way it does in maps. 0-arity and 1-arity functions are resolved at build time. Non-map, non-keyword-list values are passed through unchanged.

Associations: declare association builders with the assocs: option (see the Cookbook):

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

Each declared key is resolved before the body, so the body always receives it resolved, and the canonical Map.merge(base_params, params) ending is always correct. The builder is the default: an absent key is built with %{}, so a base_params default for a declared key is never used.

A builder is any 1-arity function (captures, local or remote, and anonymous functions), a 2-arity function that also receives the factory params (see Chaining below), or {builder, options} with either or both of:

  • default: value - what an absent key is treated as.
  • required: true - the key must resolve to a non-nil value: a caller's nil, or a builder that returns nil, raises. An absent key still builds. Singular associations only, and not together with default: nil.
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

Supplied structs and builder results must be the related schema, which Ecto provides (a builder for a singular association may return nil, unless the key is required: true). Keys must be direct associations of an Ecto schema struct:; embeds and :through associations are not supported. default: is nil or a params map for a singular association, and a list of params maps for a plural one.

assocs: is evaluated at build time, on every build, like the factory body, so it can hold local captures and anonymous functions. The factory's params variable is not in scope there. Any side effect in a default: value (an insert, a query, a sequence) therefore happens even when the caller supplies the key; keep defaults to literals. A record at an association position inside default: is rejected. The declaration is validated when the factory first builds.

Chaining: keys resolve top to bottom. A 2-arity builder receives (params, factory_params), where factory_params holds the caller's params with every key declared above resolved. The current key and the keys declared below it are hidden; undeclared keys are visible as given.

Recursion: a self-referential or mutually recursive default build (e.g. a user whose mentor builder builds a user) raises instead of recursing forever. Declare such a key with default: nil (default: [] for a list), or pass a value (a required key can only be passed a value). The guard also rejects recursion that would stop on its own while the key stays absent; supply the key at each level instead.

required: true is checked when the key resolves, which includes a nil set by a before_build_params hook. It does not check the finished struct: a body that puts nil back afterwards is not caught.

Imperative resolution: assoc/3,4 and assoc_list/3,4 apply the same rules to one key of a params map (without required: or the schema check), for plain helper functions, values computed in the body, and builders. They return the resolved value and do not modify the params map, so put it back (or drop the key) before a final Map.merge(base_params, params).

Params Functions

For struct factories, build_*_params and build_*_string_params build the struct and convert it to a clean map suitable for changesets or controller tests. For Ecto schemas, all Ecto metadata is stripped; for plain structs, the struct is converted with Map.from_struct/1:

# Returns %{username: "alice", first_name: nil, ...}
# (no __struct__, __meta__, autogenerated :id, or NotLoaded associations)
build_user_params(%{username: "alice"})

# Same but with string keys: %{"username" => "alice", ...}
build_user_string_params(%{username: "alice"})

belongs_to associations are removed from the output. If the association is persisted, its foreign key is set instead. Ordinary fields retain nil values, but nil embeds are omitted. 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 - Ecto repo for database operations
  • :extends - Parent factory module to inherit configuration from
  • :hooks - Hooks applied to all factories in the module, chained with inherited hooks

Factory-level (set with deffactory):

  • :struct - Ecto schema module (enables struct, params, and insert functions)
  • :insert? - Set to false to skip insert functions
  • :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 - Chained with the module-level hooks (see Hook Order below)
  • :repo - Overrides the module-level repo for this factory
  • :assocs - Keyword list mapping Ecto association keys to builders (see Associations above). Factory-level only.
  • :strict - Reject unknown param keys at the factory boundary (see Strict Params below)

:body and :strict cascade from the module level and are ignored by non-struct factories. :assocs is factory-level only, because association keys belong to a single schema, and it requires an Ecto schema struct:. Unknown options raise.

Strict Params

Factories can silently ignore misspelled param keys: merge-style bodies surface the typo late (in struct!/2, with an unhelpful message), and body: :struct bodies, which read params selectively, never surface it at all. Opt in to strict: true to reject unknown keys up front:

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

  Map.merge(base_params, params)
end

build_user_struct(%{usernme: "typo"})
# ** (ArgumentError) unknown params [:usernme] for strict factory :user ...

The check runs when building starts, before hooks and the factory body, against the keys of the :struct option's struct (which includes virtual fields and association keys). All derived functions (params builders, inserts, lists, and variants of the factory) are covered.

When a factory intentionally accepts keys that are not struct fields (e.g. an input used only to derive other fields), allow them explicitly:

deffactory invoice(params \\ %{}), struct: Invoice, strict: [allow: [:line_item_count]],
  body: :struct do
  %Invoice{total: Map.get(params, :line_item_count, 1) * 100}
end

Like other options, :strict can be set module-wide with use FactoryMan, strict: true and overridden per-factory (e.g. strict: false). Non-struct factories have no reference field set, so the option is ignored for them.

Hooks

Transform data at specific stages. Every factory action has both a before and after hook.

Hook Pipeline

Each generated function uses a subset of the pipeline. The full flow for insert_user is:

build_user_struct:
  params validation → before_build_params → assocs: resolution
  → [factory body + lazy eval] → 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

With body: :struct, assocs: are resolved after params validation and before the body, and the struct the body returns is lazily evaluated. Params-stage hooks remain skipped. A variant resolves its own assocs: before its body, then delegates to the base builder, which reuses the resolved structs. insert_*_struct receives an already-built struct and does not resolve associations.

For non-struct factories, build_* runs before_build_params, the factory body with lazy evaluation, then after_build_params.

insert_user_struct/1,2 runs the same before_insert → insert → after_insert pipeline on an already-built struct. Use it after modifying a built struct, so records are shaped consistently no matter how they were constructed. A raw Repo.insert!/2 would skip the insert hooks.

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 struct just before database insertion
:after_insertstructstructPost-process after insertion (e.g. reset associations)

A hook is a 1-arity remote capture such as &__MODULE__.my_hook/1 or &MyApp.Hooks.my_hook/1. Hooks are compiled into the generated functions, so anything that is not a remote capture, such as an anonymous function (fn ... end), raises at compile time.

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. A parent's after_insert that resets associations therefore sees the final struct, after any factory after_insert.

To place a hook 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-

A plain hook uses the default placement for its hook name. "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 an inherited hook for one factory, replace it with an identity function: hooks: [after_insert: {&Function.identity/1, :replace_parent}].

__factory_man__(:opts) and __factory_man__(:opts, name) show each hook name's resolved list, in run order. Unknown hook names, a hook name set twice at one level, and invalid hook values raise.

Examples

Reset associations after insert:

defmodule MyApp.Factory do
  use FactoryMan,
    repo: MyApp.Repo,
    hooks: [after_insert: &__MODULE__.reset_assocs/1]

  def reset_assocs(struct) do
    Ecto.reset_fields(struct, struct.__struct__.__schema__(:associations))
  end
end

Log factory usage for debugging:

deffactory user(params \\ %{}), struct: User,
  hooks: [after_build_params: &__MODULE__.log_params/1] do
  base_params = %{username: "user-#{System.os_time()}"}

  Map.merge(base_params, params)
end

def log_params(params) do
  IO.inspect(params, label: "factory params")
  params
end

Factory Inheritance

Child factories inherit the parent's repo, hooks, and helper functions via :extends:

defmodule MyApp.Factory do
  use FactoryMan, repo: MyApp.Repo
  def generate_username, do: "user-#{System.os_time()}"
end

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

  deffactory user(params \\ %{}), struct: User do
    base_params = %{username: generate_username()}

    Map.merge(base_params, params)
  end
end

Inheritance chains are unlimited; a child factory can itself be extended.

Variant Factories (defvariant)

A variant wraps an existing base factory. It transforms the caller's params before passing them to the base factory. Think of it as a preprocessor: the variant runs first, then the base factory runs with the transformed params.

This ordering can be counterintuitive because the variant is defined after the base factory in your code, but its logic executes before the base factory at runtime:

Code order:     deffactory user(...)   ->  defvariant admin(...), for: :user
Execution order:  admin (preprocessor)  ->  user (base factory)

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

Calling build_admin_user_struct() is equivalent to build_user_struct(%{role: "admin"}). Calling build_admin_user_struct(%{role: "superadmin"}) passes %{role: "superadmin"} to the base factory because the caller's params override the variant defaults.

Generated functions follow the pattern {variant}_{base}: build_admin_user_params/0,1, build_admin_user_struct/0,1, insert_admin_user/0,1,2, plus list variants.

Custom naming with :as

The :as option overrides the combined {variant}_{base} name:

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

  Map.merge(base_params, params)
end

This generates build_mod_struct/0,1, insert_mod/0,1,2, etc. instead of the default build_moderator_user_struct.

Sequences

Generate sequential values or cycle through a list:

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

Reset in test setup: FactoryMan.Sequence.reset()

Lazy Evaluation

Functions in factory params are evaluated at build time. This works in both maps and keyword lists:

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

# In keyword lists
[
  created_at: fn -> DateTime.utc_now() end,
  label: fn kw -> "timeout-#{kw[:timeout]}" end          # 1-arity: receives parent keyword list
]

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

Factories for embedded schemas work like regular struct factories but without database insertion:

defmodule MyApp.Factories.Settings do
  use FactoryMan, extends: MyApp.Factory

  alias MyApp.Users.Settings

  deffactory settings(params \\ %{}), struct: Settings do
    base_params = %{
      theme: "dark",
      notifications: true
    }

    Map.merge(base_params, params)
  end
end

Embedded schemas generate build_*_struct, build_*_params, and build_*_string_params, plus their matching *_list functions. They do not generate insert functions.

Direct Struct Factories (body: :struct)

For complex factories that need full control over struct construction, set body: :struct. The factory body returns a struct directly instead of a params map:

deffactory invoice(params \\ %{}), struct: Invoice, body: :struct do
  customer =
    case params[:customer] do
      %Customer{} = customer -> customer
      _ -> MyApp.Factory.Accounts.insert_customer()
    end

  %Invoice{
    customer: customer,
    total: Map.get(params, :total, Enum.random(100..10_000))
  }
end

This generates the full function family, including build_invoice_params (derived from the built struct). Lazy values in the returned struct are resolved as they are in a params body. The after_build_struct, before_insert, and after_insert hooks still run. The before_build_params, after_build_params, and before_build_struct hooks are skipped since there is no params-to-struct conversion stage.

body: :struct can also be set at the module level with use FactoryMan, body: :struct, then overridden per-factory with body: :params if needed. Non-struct factories in the same module are unaffected; their build_* functions are always generated.

Cookbook

A practical progression from a first factory through realistic defaults, variants, associations, suite organization, hooks, strict params, and advanced presets lives in the Cookbook guide.

Reflection and Debugging

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

iex> MyApp.Factory.__factory_man__(:opts)
[repo: MyApp.Repo]

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

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

assocs: is code evaluated at build time, so it does not appear in :opts. :factories lists every factory and variant registered in the module (variants under their full name), which enables runtime dispatch without string-building function names:

def build_any(factory_module, factory_name, params) do
  if factory_name not in factory_module.__factory_man__(:factories) do
    raise ArgumentError, "unknown factory #{inspect(factory_name)}"
  end

  apply(factory_module, :"build_#{factory_name}_struct", [params])
end

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 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? - Set to false to skip generating insert functions (default: true when repo is configured and struct is insertable)
  • :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 section)
  • :repo - Overrides the module-level repo for this factory
  • :assocs - A keyword list mapping Ecto association keys to builders: a 1-arity function, a 2-arity function (params, factory_params), or {builder, options} with default: value and/or required: true. Each key is resolved before the body, which always receives it resolved; an absent key is built with %{} (or treated as default:). Evaluated on every build; params is not in scope. Requires an Ecto schema struct:. Direct associations only. See the Associations section in the module documentation.
  • :strict - Set to true to raise on param keys that are not fields of the :struct option's struct, or [allow: [...]] to permit specific extra keys (see the Strict Params section). Ignored for non-struct factories.

Generated Functions

For a factory named user with struct: User and a plain params argument that defaults to an empty map, the following functions are generated. Insert functions require a configured repo, a table-backed Ecto schema, and insert? not set to false.

  • build_user_struct/0,1 - Returns an unsaved struct
  • build_user_params/0,1 - Clean params map derived from the built struct
  • build_user_string_params/0,1 - Same, with string keys
  • insert_user/0,1,2 - Inserts into the database (when repo is configured)
  • build_user_struct_list/1,2 - Builds multiple structs
  • build_user_params_list/1,2 - Builds multiple params maps
  • build_user_string_params_list/1,2 - Builds multiple string-keyed params maps
  • insert_user_list/1,2,3 - Inserts multiple items (when repo is configured)
  • insert_user_struct/1,2 - Inserts an already-built struct through the insert pipeline

For a factory named greeting without struct:, simplified names are used:

  • build_greeting/1 - Returns the factory's value
  • build_greeting_list/2 - Builds multiple items

With a default argument in the factory head, build_greeting/0 and build_greeting_list/1 are also 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, 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, 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)

  • :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 registered under their full name, so a variant can itself serve as the base of another variant (e.g. defvariant senior(params \\ %{}), for: :admin_user).

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

def user_factory do
  %{
    username: FactoryMan.sequence("joe")
  }
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

def user_factory do
  %{
    email: FactoryMan.sequence(:email, fn n -> "me-#{n}@foo.com" end)
  }
end

Example with a list

def user_factory do
  %{
    name: FactoryMan.sequence(:name, ["Joe", "Mike", "Sarah"])
  }
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

def money_factory do
  %{
    cents: FactoryMan.sequence(:cents, fn n -> "#{n}" end, start_at: 600)
  }
end