FactoryMan (Factory Man v0.8.0)
View SourceAn Elixir library for generating test data. Define factories with deffactory, and FactoryMan
generates functions for building params, structs, and 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
endGenerated Functions
For a factory named :user with struct: User:
| Function | Returns | Purpose |
|---|---|---|
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 |
All functions accept optional params for customization. Insert functions also accept repo options. Each item in a list is evaluated independently (unique timestamps, sequences, etc.).
What gets generated depends on the options:
| Options | Params | Struct | Insert |
|---|---|---|---|
struct: User (default) | Yes | Yes | Yes |
No struct: option | Yes | No | No |
insert?: false | Yes | Yes | No |
body: :struct | Yes | Yes | Yes |
| Embedded schema | Yes | Yes | No |
Params functions are derived from the built struct, so they exist for every struct factory —
including body: :struct factories, whose body returns a struct directly.
How the functions relate (list variants omitted — each generated function also has a *_list
counterpart that evaluates every item independently):
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_fnDefining Factories
The deffactory macro works like defining a function — specify a name, a parameter, and a body
that returns a plain map. For struct factories, the map's keys must be fields of the
struct (it is passed to struct!/2):
deffactory user(params \\ %{}), struct: User do
base_params = %{username: "user-#{System.os_time()}"}
Map.merge(base_params, params)
endYou 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)
endStruct 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 type | Generated 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)
endLazy 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 — resolve related records with assoc/4 (see the Cookbook section):
deffactory author(params \\ %{}), struct: Author do
base_params = %{name: "Test Author"}
params = Map.put(params, :user, assoc(params, :user, &build_user_struct/1, struct: User))
Map.merge(base_params, params)
endResolve into params rather than into the defaults map: the final Map.merge gives params
the last word, so a value resolved only in the defaults would be clobbered by the caller's
raw input.
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, the
foreign key is set instead. Unlike ExMachina, nil values are preserved (a nil field may be
intentional), and struct values like 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:suppress_duplicate_option_warning— Suppress warnings for redundant options
Factory-level (set with deffactory):
:struct— Ecto schema module (enables struct, params, and insert functions):insert?— Set tofalseto 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). Ignored for non-struct factories.:hooks— Merged with module-level hooks:suppress_duplicate_option_warning— Suppress warnings for redundant options
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:
before_build_params → [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_insertFor 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
| 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 struct just before database insertion |
:after_insert | struct | struct | Post-process after insertion (e.g. reset associations) |
Hook Precedence
Hooks can be set at three levels. Later levels override earlier ones for the same hook key:
- Parent module —
use FactoryMan, hooks: [...] - Child module —
use FactoryMan, extends: Parent, hooks: [...] - Individual factory —
deffactory name(params), hooks: [...]
Examples
Reset associations after insert (most common hook usage):
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
endLog 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
endFactory 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
endInheritance 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: 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)
endCalling 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)
endThis generates build_mod_struct/0,1, insert_mod/0,1,2, etc.
— instead of the default build_moderator_user_struct.
Sequences
Generate unique values across builds:
sequence("user") # "user0", "user1", ...
sequence(:email, fn n -> "user#{n}@example.com" end) # custom formatter
sequence(:role, ["admin", "moderator", "user"]) # cycles through list
sequence(:order, fn n -> "ORD-#{n}" end, start_at: 1000) # custom start valueReset 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
1-arity functions receive the map or keyword list before lazy evaluation. Don't reference other lazy fields — they'll still be function references, not resolved values.
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
endEmbedded schemas generate build_*_params and build_*_struct functions only (as well as
the matching *_list functions), but do not generate any 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))
}
endThis generates the full function family, including build_invoice_params (derived from the
built struct). 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
Building associations
Use assoc/4 to let callers pass a prebuilt association, params to build one from, or
nothing at all:
deffactory post(params \\ %{}), struct: Post do
base_params = %{title: sequence("post")}
params =
Map.put(params, :author, assoc(params, :author, &build_author_struct/1, struct: Author))
Map.merge(base_params, params)
end
build_post_struct() # builds a default author
build_post_struct(%{author: my_author}) # reuses the given struct
build_post_struct(%{author: %{name: "Ann"}}) # builds an author from paramsIn merge-style factories, resolve into params (as above), not into the defaults map — the
final Map.merge would clobber a resolved default with the caller's raw input. Factories
with body: :struct don't merge, so they can use assoc/4 directly in field position.
The struct: option raises on a struct of the wrong type — without it, a mistyped value
would be reused silently. See assoc/4 for the full semantics (:inherit defaults,
optional associations via on_nil: :keep and on_missing: nil) and assoc_list/4 for
has_many-style lists.
When the schema only needs a foreign key (and the record must exist), insert the association and use its ID:
deffactory comment(params \\ %{}), struct: Comment do
base_params = %{
body: "Nice post!",
post_id: Map.get_lazy(params, :post_id, fn -> insert_post().id end)
}
Map.merge(base_params, params)
endPost-build presets
Variants preprocess params — they cannot transform the built value. When a preset needs
to derive fields from the built struct, define a separate factory with body: :struct
whose body calls the base build function and transforms the result:
deffactory anonymized_user(params \\ %{}), struct: User, body: :struct do
user = build_user_struct(params)
%{user | email: "redacted+#{user.id}@example.com", display_name: "anonymous"}
endUnlike a hand-written build_anonymized_user/1 function, this keeps the whole generated
family: build_anonymized_user_params, the _list builders, and insert_anonymized_user
all come for free.
One caveat: an after_build_struct hook runs twice — once inside the base build call and
once for the wrapping factory. Insert hooks run once (only the wrapping factory's insert
is called).
Validated presets
A variant that promises a property of its result ("this post is always published") can be silently broken by caller params. To make the promise explicit, assert it over the merged result and raise:
defvariant published(params \\ %{}), for: :post do
base_params = %{published_at: DateTime.utc_now(), draft: false}
result_params = Map.merge(base_params, params)
if Post.published?(result_params),
do: result_params,
else: raise("params contradict the published preset")
endA misuse like build_published_post_struct(%{draft: true}) now fails at the factory
boundary instead of producing a self-contradictory record. The same shape works in the
opposite polarity (a draft variant raising when the params imply a published post).
Note the check runs on raw params, before lazy evaluation — the predicate cannot see resolved values of lazy (function) fields.
Duplicate Option Warnings
If a child factory module specifies an option that is already defined by its parent with the same value, FactoryMan will emit a compile-time warning. This helps catch redundant options that were likely copy-pasted from the parent.
To suppress the warning for a specific module or factory, add
suppress_duplicate_option_warning: true to the options.
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]: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
Merge child factory options into parent options.
Warn at compile time if child opts contain options that are already defined by the parent with the same value.
Resolve an association value from factory params.
Resolve a list of association values from factory params.
Defines a factory that generates test data.
Defines a variant factory that wraps a base factory.
Evaluate lazy attributes in a map, struct, or keyword list.
The default handler for hooks. This function is a no-op, and simply returns the given value
without any modifications.
Get the configured handler for a hook, or fall back to &FactoryMan.fallback_hook_handler/1.
Generates a sequence of strings.
Generates and returns a unique sequence.
Generates and returns a unique sequence with options.
Functions
Merge child factory options into parent options.
Most options are overridden per-key, but :hooks are merged per hook key so that a child
setting one hook does not discard the parent's other hooks.
This is a FactoryMan internal function — called from macro-generated code. Use the underscore prefix convention to signal that it is not part of the public API.
Warn at compile time if child opts contain options that are already defined by the parent with the same value.
This is a FactoryMan internal function — called from macro-generated code. Use the underscore prefix convention to signal that it is not part of the public API.
Resolve an association value from factory params.
Factories commonly accept an association as either a prebuilt struct, a params map to build
one from, or nothing at all. assoc/4 resolves all of those with one call:
deffactory post(params \\ %{}), struct: Post do
base_params = %{title: sequence("post")}
params =
Map.put(params, :author, assoc(params, :author, &build_author_struct/1, struct: Author))
Map.merge(base_params, params)
endIn merge-style factories, resolve into params (as above) so the final Map.merge keeps the
resolved value; body: :struct factories can call assoc/4 directly in field position.
params[key] | Result |
|---|---|
| key absent | build_fun.(inherit) |
key absent (with on_missing: nil) | nil |
nil (with on_nil: :build) | build_fun.(inherit) |
nil (with on_nil: :keep) | nil |
struct matching :struct | reused as-is |
struct not matching :struct | raises ArgumentError |
any struct (no :struct option) | reused as-is |
| params map | build_fun.(Map.merge(inherit, map)) |
| anything else | raises ArgumentError |
build_fun is any 1-arity function — a build_* function for in-memory associations, or an
insert_* function when the association must be persisted.
Options
:struct— the struct module a passed-in struct must match; any other struct type raises. Recommended whenever the association has a known type: without it, a struct of the wrong type is reused without complaint.:inherit— default params for the built association (default:%{}). Used as thebuild_funargument when building, and merged under a caller-supplied params map (the caller's keys win).:on_nil— what an explicitnilvalue means::build(default) treats it like a missing key;:keepreturnsnil, for optional associations.:on_missing— what a missing key means::build(default) builds the default association;nilreturnsnil, for associations that only exist when the caller supplies one. Independent of:on_nil; sites treating "absent" and "explicitnil" the same typically pairon_missing: nilwithon_nil: :keep.
Examples
# Nothing passed — the default association is built
assoc(%{}, :author, &build_author_struct/1)
# A struct is reused as-is
assoc(%{author: %Author{name: "Ann"}}, :author, &build_author_struct/1, struct: Author)
# A params map builds the association from those params
assoc(%{author: %{name: "Ann"}}, :author, &build_author_struct/1, struct: Author)
Resolve a list of association values from factory params.
A missing key or nil resolves to []. Otherwise the value must be a list, and each element
is resolved independently with assoc/4 semantics: structs are reused (and type-checked
against :struct when given), params maps are built via build_fun, and anything else
raises. Mixed lists are fine.
Accepts the :struct and :inherit options from assoc/4.
Examples
deffactory post(params \\ %{}), struct: Post do
base_params = %{title: sequence("post")}
params =
Map.put(
params,
:comments,
assoc_list(params, :comments, &build_comment_struct/1, struct: Comment)
)
Map.merge(base_params, params)
end
# Two comments built from params, one reused
build_post_struct(%{comments: [%{body: "a"}, %{body: "b"}, existing_comment]})
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 tofalseto skip generating insert functions (default:truewhen 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). Ignored for non-struct factories.:hooks- A keyword list of hook functions to apply at different stages (see Hooks section):suppress_duplicate_option_warning- Set totrueto suppress warnings when this factory specifies an option already defined by the module with the same value
Generated Functions
For a factory named user with struct: User, the following functions are generated:
build_user_struct/0,1- Returns an unsaved structbuild_user_params/0,1- Clean params map derived from the built structbuild_user_string_params/0,1- Same, with string keysinsert_user/0,1,2- Inserts into the database (when repo is configured)build_user_struct_list/1,2- Builds multiple structsbuild_user_params_list/1,2- Builds multiple params mapsbuild_user_string_params_list/1,2- Builds multiple string-keyed params mapsinsert_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 valuebuild_greeting_list/2- Builds multiple items
Examples
deffactory user(params \\ %{}), struct: User do
base_params = %{
username: sequence("user"),
email: 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 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: 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)
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).
Evaluate lazy attributes in a map, struct, or keyword list.
Functions with 0 arity are called with no arguments. Functions with 1 arity receive the parent factory (map, struct, or keyword list) as their argument.
Non-map, non-keyword-list values are passed through unchanged.
Examples
iex> FactoryMan.evaluate_lazy_attributes(
...> %{name: "test", timestamp: fn -> System.os_time() end}
...> )
%{name: "test", timestamp: 12345}
iex> FactoryMan.evaluate_lazy_attributes(
...> %{first: "John", last: fn attrs -> attrs.first <> " Smith" end}
...> )
%{first: "John", last: "John Smith"}
iex> FactoryMan.evaluate_lazy_attributes(
...> [timeout: 5000, created_at: fn -> DateTime.utc_now() end]
...> )
[timeout: 5000, created_at: ~U[2026-01-01 00:00:00Z]]
iex> FactoryMan.evaluate_lazy_attributes("plain string")
"plain string"
The default handler for hooks. This function is a no-op, and simply returns the given value
without any modifications.
Examples
iex> FactoryMan.fallback_hook_handler(123)
123
Get the configured handler for a hook, or fall back to &FactoryMan.fallback_hook_handler/1.
Examples
iex> hooks = [after_insert: &YourProject.Factories.Users.user_after_insert_handler/1]
iex> FactoryMan.get_hook_handler(hooks, :before_build)
&FactoryMan.fallback_hook_handler/1
iex> FactoryMan.get_hook_handler(hooks, :after_insert)
&YourProject.Factories.Users.user_after_insert_handler/1
Generates a sequence of strings.
The sequence name is used as the beginning of the string. For example, if you
do sequence("joe"), you will get back "joe0", then "joe1", and so on.
Example
def user_factory do
%{
username: sequence("joe")
}
endIf you want to customize the returned string you can use sequence/2.
Generates and returns a unique sequence.
If a formatter function is passed, it will be called with the current position of the sequence. You can also pass a list, and each item in the list will be returned in sequence.
Example with a formatter function
def user_factory do
%{
email: sequence(:email, fn n -> "me-#{n}@foo.com" end)
}
endExample with a list
def user_factory do
%{
name: sequence(:name, ["Joe", "Mike", "Sarah"])
}
end
@spec sequence(any(), (integer() -> any()) | [...], [{:start_at, non_neg_integer()}]) :: any()
Generates and returns a unique sequence with options.
Currently, the only option is :start_at which specifies the number to
start the sequence at.
Example
def money_factory do
%{
cents: sequence(:cents, fn n -> "#{n}" end, start_at: 600)
}
end