# `SeedFactory`
[🔗](https://github.com/fuelen/seed_factory/blob/v0.10.1/lib/seed_factory.ex#L1)

A toolkit for test data generation.

The main idea of `SeedFactory` is to generate data in tests according to your application business logic (read as
context functions if you use [Phoenix Contexts](https://hexdocs.pm/phoenix/contexts.html)) whenever possible
and avoid direct inserts to the database (as opposed to `ex_machina`).
This approach allows you to minimize testing of invalid states as you're not forced to keep complex database structure in your head in order to prepare test data.
The library is completely agnostic to the database toolkit.

## Core concepts

**Context** is a `t:map/0` which can be populated with entities using commands.

The schema with instructions on how commands modify context is described using DSL with the help of `SeedFactory.Schema` module.

**Commands** can be used to:
* produce entity (put new data in the context)
* update entity (replace the existing entity in the context)
* delete entity (remove the entity from the context)

A command has params with instructions on how to generate arguments for a resolver if they are not passed explicitly with `exec/3` function.
The instruction can be specified using one of these options:
* `:value` - any term. This option is used by default with the value of `nil`.
* `:generate` - a zero-arity function for generating data.
* `:entity` - an atom which points to an entity which should be taken from the context. If a required entity cannot
be found in a context, then `SeedFactory` automatically executes a corresponding command which produces the entity.
The `:entity` option also supports `:with_traits` to require that the entity has specific traits before it can be used as a dependency.

**Traits** are labels assigned to entities when specific commands with specific arguments are executed.
They describe how an entity was created or what state it is in. Use `from` for state transitions:
an `:active` trait declared with `from :pending` replaces `:pending`. Omit `from` to add a trait without
replacing another. Traits can also be tied to specific argument values using `args_pattern`.

## Schema example

```elixir
defmodule MyApp.SeedFactorySchema do
  use SeedFactory.Schema

  command :create_company do
    param :name, generate: &Faker.Company.name/0

    resolve(fn args ->
      with {:ok, company} <- MyApp.Companies.create_company(args) do
        {:ok, %{company: company}}
      end
    end)

    produce :company
  end

  command :create_user do
    param :name, generate: &Faker.Person.name/0
    param :role, value: :normal
    param :company, entity: :company

    resolve(fn args -> MyApp.Users.create_user(args.company, args.name, args.role) end)

    produce :user
    produce :profile
  end

  command :activate_user do
    # with_traits ensures the user has the :pending trait before activation
    param :user, entity: :user, with_traits: [:pending]

    resolve(fn args ->
      user = MyApp.Users.activate_user!(args.user)

      {:ok, %{user: user}}
    end)

    update :user
  end

  # :pending is assigned when :create_user is executed
  trait :pending, :user do
    exec :create_user
  end

  # :activate_user replaces :pending with :active
  trait :active, :user do
    from :pending
    exec :activate_user
  end

  # :admin and :normal are determined by the :role argument
  trait :admin, :user do
    exec :create_user, args_pattern: %{role: :admin}
  end

  trait :normal, :user do
    exec :create_user, args_pattern: %{role: :normal}
  end
end
```

## Getting started

To start using the schema, put metadata about it to the context using `init/2` function:
```elixir
context = %{}
context = init(context, MyApp.SeedFactorySchema)
```
If you use `SeedFactory` in tests with `ExUnit`, check out `SeedFactory.Test`. This module adds initialization using `ExUnit.Callbacks.setup_all/2` callback
and imports functions.

## exec

`exec/2` function can be used to execute a command:
```elixir
context = exec(context, :create_company)
```
The code above will generate arguments for `:create_company` command, execute a resolver with generated arguments and put company to context using `:company` key.
`exec/3` can be used if you want to specify parameters explicitly:
```elixir
context = exec(context, :create_company, name: "GitHub")
```

Because exec function returns `t:context/0`, it is convenient to chain `exec` calls with the pipe operator:
```elixir
context =
  context
  |> exec(:create_company)
  |> exec(:create_user, name: "John Doe")
```
In order to get a value for the `:company` parameter of the `:create_user` command, the corresponding entity was taken from the context.
However, it is not necessary to do so, as `SeedFactory` can automatically execute commands which produce dependent entities.
The code above has the same effect as a single call to `:create_user` command:
```elixir
context = exec(context, :create_user, name: "John Doe")
```

## produce

If you're not interested in explicitly providing parameters to commands, you can use `produce/2` to produce
requested entities with automatic execution of all dependent commands:
```elixir
context = produce(context, :user)
```
Even though `:user` is the only entity specified explicitly, `context` will have 3 new keys: `:company`, `:user` and `:profile`.

Traits can be specified to control how entities are created. SeedFactory figures out
the command chain from the trait definitions:
```elixir
# these two are equivalent
context = produce(context, user: [:admin, :active])

context =
  context
  |> exec(:create_user, role: :admin)
  |> exec(:activate_user)
```

Use the `:as` option to combine traits with rebinding:
```elixir
%{active_admin: active_admin} = produce(context, user: [:admin, :active, as: :active_admin])
```

> #### Tip {: .tip}
> Pattern match on what you pass to `produce/2`. This makes the test self-documenting
> and avoids relying on implicitly created entities:
> ```elixir
> # good — matches what was requested
> %{user: user} = produce(context, :user)
>
> # good — multiple entities requested and matched
> %{user: user, profile: profile} = produce(context, [:user, :profile])
>
> # bad — matching entities that were not explicitly requested
> %{user: user, profile: profile, company: company} = produce(context, :user)
> ```

## Rebinding

`exec/3` fails if produced entities are already present in the context.
It is possible to rebind entities in order to assign them to the context with different names:
```elixir
context =
  context
  |> rebind([user: :user1, profile: :profile1], &exec(&1, :create_user))
  |> rebind([user: :user2, profile: :profile2], &exec(&1, :create_user))
```
The snippet above puts the following keys to the context: `:company`, `:user1`, `:profile1`, `:user2` and `:profile2`.
The `:company` is shared in this case, so two users have different profiles and belong to the same company.
A shorter counterpart using `produce/2` is the following:
```elixir
context =
  context
  |> produce(user: :user1, profile: :profile1)
  |> produce(user: :user2, profile: :profile2)
```

## Debugging

`SeedFactory` tracks how each entity was created and modified.
You can inspect `__seed_factory_meta__` key in the context to review currently assigned traits and execution history:

```elixir
context |> produce(user: [:admin, :active]) |> IO.inspect()
# %{
#   __seed_factory_meta__: #SeedFactory.Meta<
#     current_traits: %{company: [], profile: [], user: [:admin, :active]},
#     trails: %{
#       company: #trail[:create_company],
#       profile: #trail[:create_user],
#       user: #trail[create_user: +[:admin, :pending] -> activate_user: +[:active] -[:pending]]
#     },
#     execution_history: [
#       #execution[produce(user: [:admin, :active]): create_company → create_user → activate_user]
#     ],
#     ...
#   >,
#   company: %Company{...},
#   profile: %Profile{...},
#   user: %User{...}
# }
```

If an exception or error occurs during command execution, `SeedFactory` raises `SeedFactory.ExecError`
with structured metadata including the execution plan, trails, and current traits at the time of failure:

```
** (SeedFactory.ExecError) unable to execute :create_user command: :email_already_taken

Execution plan:
  ✔ :create_company
  ✖ :create_user

Current traits:
  %{company: []}
```

# `context`

```elixir
@type context() :: map()
```

# `entity_name`

```elixir
@type entity_name() :: atom()
```

# `rebinding_rule`

```elixir
@type rebinding_rule() :: {entity_name(), rebind_as :: atom()}
```

# `trait_name`

```elixir
@type trait_name() :: atom()
```

# `trait_request`

```elixir
@type trait_request() ::
  trait_name() | {trait_name(), value :: term()} | {:as, rebind_as :: atom()}
```

# `exec`

```elixir
@spec exec(context(), command_name :: atom(), initial_input :: map() | keyword()) ::
  context()
```

Executes a command and updates the `context` according to the schema.

## Example

    iex> context = %{}
    ...> context = init(context, MySeedFactorySchema)
    ...> context = exec(context, :create_user, first_name: "John", last_name: "Doe")
    ...> Map.take(context.user, [:first_name, :last_name])
    %{first_name: "John", last_name: "Doe"}

# `init`

```elixir
@spec init(context(), schema :: module()) :: context()
```

Puts metadata about `schema` to `context`, so `context` becomes usable by other functions from this module.

## Example

    iex> context = %{}
    ...> init(context, MySeedFactorySchema)
    %{__seed_factory_meta__: #SeedFactory.Meta<...>}

# `pre_exec`

```elixir
@spec pre_exec(context(), command_name :: atom(), initial_input :: map() | keyword()) ::
  context()
```

Creates all dependent entities needed for a command, without executing the command itself.

This is useful when you want to execute the same command multiple times with shared dependencies.
It is especially handy when a command has many dependencies — `pre_exec` resolves all of them
automatically, so you don't have to enumerate them yourself.

## Example

Consider a schema where `:publish_article` depends on `:blog`, `:category`,
and `:author` (with trait `:active`):

    command :publish_article do
      param :blog, entity: :blog
      param :category, entity: :category
      param :author, entity: :author, with_traits: [:active]
      param :title, generate: &Faker.Lorem.sentence/0
      # ...
      produce :article
    end

Without `pre_exec`, you would have to manually set up all the dependencies:

    context = produce(context, [:blog, :category, author: [:active]])

    %{article: article1} = exec(context, :publish_article, title: "First")
    %{article: article2} = exec(context, :publish_article, title: "Second")

With `pre_exec`, the same is achieved without knowing the dependency tree:

    context = pre_exec(context, :publish_article)

    %{article: article1} = exec(context, :publish_article, title: "First")
    %{article: article2} = exec(context, :publish_article, title: "Second")

# `pre_produce`

```elixir
@spec pre_produce(
  context(),
  entity_name()
  | [entity_name() | rebinding_rule() | {entity_name(), [trait_request()]}]
) :: context()
```

Produces all dependencies needed for specified entities, without producing the entities themselves.

Works like `pre_exec/3`, but operates on entities instead of commands. The dependency tree
is derived from the commands that would produce the requested entities.

When traits are specified, the full command chain is analyzed — including update commands.
All their dependencies are created, but the requested entity itself is not.
For example, if `:activate_user` updates `:user` and depends on `:team`,
then `pre_produce(context, user: [:active])` will create `:team` (and all other
dependencies in the chain) without creating the `:user`.

## Examples

    # Create all dependencies for :article (e.g. :blog, :category, :author)
    # without creating the article itself
    context = pre_produce(context, :article)

    %{article: article1} = produce(context, :article)
    %{article: article2} = produce(context, :article)

Accepts a list with traits, same as `produce/2`:

    context = pre_produce(context, [:article, user: [:active, :admin]])

# `produce`

```elixir
@spec produce(
  context(),
  entity_name()
  | [entity_name() | rebinding_rule() | {entity_name(), [trait_request()]}]
) :: context()
```

Produces entities with their dependencies resolved automatically.

Analyzes the schema to determine which commands need to be executed and in what order.
Entities that already exist in the context are reused. The order of specified entities
doesn't matter.

## Examples

    # produce a single entity (dependencies like :company are created automatically)
    %{user: _} = produce(context, :user)

    # produce multiple entities
    %{user: _, company: _} = produce(context, [:user, :company])

    # rebind entity to a different name
    %{user1: _} = produce(context, user: :user1)

    # specify traits
    %{user: _} = produce(context, user: [:active, :admin])

    # pass a value to a trait declared with args_pattern: %{age: _}
    %{user: _} = produce(context, user: [:active, age: 18])

    # apply traits incrementally — first create a pending admin, then activate
    %{user: _} = context |> produce(user: [:pending, :admin]) |> produce(user: [:active])

    # combine traits with rebinding using :as option
    %{user1: _} = produce(context, user: [:active, :admin, as: :user1])

## Multiple commands producing the same entity

If the same entity can be produced by multiple commands, the first declared command
is used by default. Traits allow you to pick a specific command. For example, given:

    command :create_draft_project do
      # ...
      produce :draft_project
    end

    command :import_draft_project do
      # ...
      produce :draft_project
    end

    trait :created, :draft_project do
      exec :create_draft_project
    end

    trait :imported, :draft_project do
      exec :import_draft_project
    end

Then:

    # uses :create_draft_project (first declared) and assigns the :created trait
    produce(context, :draft_project)

    # equivalent to the above, but explicit
    produce(context, draft_project: [:created])

    # uses :import_draft_project
    produce(context, draft_project: [:imported])

Requested traits also influence how other entities in the plan are produced.
A command that satisfies a requested trait is preferred for every entity it
produces, so related entities come from the same command whenever possible.
The declaration order decides among equally preferred commands. When the
preferred command cannot be part of the plan, another command that produces
the entity is used.

# `rebind`

```elixir
@spec rebind(context(), [rebinding_rule()], (context() -&gt; context())) :: context()
```

Creates a scoped context where entities are assigned under different names.

Within the callback, all commands that produce, update, delete, or depend on
the specified entities will use the remapped names. The rebinding is scoped to
the callback — it does not affect the context outside of it.

This is useful when you need to produce the same entity multiple times and
control how they relate to each other. For simpler cases where you don't need
to control relationships, `produce/2` with rebinding is enough:

    context |> produce(company: :company1) |> produce(company: :company2)

## Example

    # :product requires :company. Here we create 2 companies and 1 product.
    # Inside the first rebind, :company refers to :company1 and :product to :product1,
    # so the product is linked to company1.
    %{company1: _, company2: _, product1: _} =
      context
      |> rebind([company: :company1, product: :product1], fn context ->
        context
        |> exec(:create_company, name: "GitHub")
        |> produce(:product)
      end)
      |> rebind([company: :company2], fn context ->
        exec(context, :create_company, name: "Microsoft")
      end)

---

*Consult [api-reference.md](api-reference.md) for complete listing*
