README.md

# FactoryMan

Elixir test data factories with automatic struct building, database insertion, and customizable hooks.

> NOTE: FactoryMan is heavily inspired by [ExMachina](https://hex.pm/packages/ex_machina), and copies some code from it in some cases (e.g. for sequences). However, FactoryMan is not a clone of ExMachina. See the examples in the documentation to see what sets us apart!

## Features

- Easy factories - Define factories with minimal boilerplate
- Generated functions - Build params (for testing changesets), structs, and lists of items
- Database insertion - Built-in `insert_` functions with configurable repo
- List factories - Create multiple records with `*_list` functions
- Sequence generation - Automatic unique value generation for usernames, emails, etc.
- Lazy evaluation - Compute values at build time
- Factory inheritance - Share config from parent factories to reduce boilerplate
- Hooks - Apply custom transformations to data before/after building or inserting a factory item

## Installation

Add FactoryMan to your `mix.exs` dependencies:

```elixir
def deps do
  [
    {:factory_man, "0.2.0"}
  ]
end
```

Then run `mix deps.get`.

No further configuration should be necessary.

## Quick Start

Create a factory module in your project's `test/support/` directory:

`test/support/factory.ex`
```elixir
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
```

Build and insert some test data:

```elixir
# Build params (e.g. for testing changesets)
iex> MyApp.Factory.build_user_params(%{username: "test_user"})
%{username: "test_user"}

# Build a struct (not persisted)
iex> MyApp.Factory.build_user_struct(%{username: "test_user"})
%User{id: nil, username: "test_user"}

# Insert into the database
iex> MyApp.Factory.insert_user!(%{username: "test_user"})
%User{id: 1, username: "test_user"}

# Insert multiple items in a single statement
iex> MyApp.Factory.insert_user_list!(3)
[%User{id: 1, ...}, %User{id: 2, ...}, %User{id: 3, ...}]
```

## Base Factory Pattern (Optional)

For larger projects, you may want to share common configuration across multiple factory modules.
FactoryMan allows you to create a base factory module with shared settings:

```elixir
defmodule MyApp.Factory do
  # Define the base factory options here
  use FactoryMan, repo: MyApp.Repo

  # You may define generic factory helpers in this module as well
  def generate_username, do: "user-#{System.os_time()}"
end
```

Then extend the base factory in child factory modules:

```elixir
defmodule MyApp.Factory.ChildFactory do
  # Extend the base factory
  use FactoryMan, extends: MyApp.Factory

  alias MyApp.Factory
  alias MyApp.Users.User

  # Child factories in this module inherit the options set in the base factory module
  deffactory user(params \\ %{}), struct: User do
    %{username: Factory.generate_username()} |> Map.merge(params)
  end
end
```

Child factories are typically placed in `test/support/factory/[your_context].ex` and extend the
base factory to inherit common configuration like repo settings and hooks.

The directory structure is up to you, but it is recommended to make a factory module for each
context in your application code. Keeping the filesystem hierarchies the same tends to make it
easier to remember which factory is where.

> #### Tip {: .tip}
>
> The base factory pattern is completely optional. Use whatever structure fits your project or personal tastes.

### Recommended Project Structure

> #### Note {: .info}
>
> This is an opinionated recommendation, not a requirement. FactoryMan is intended to work with
> any module structure. However, having a consistent convention reduces decision fatigue and makes
> it easier to navigate factory code across projects.

We recommend keeping the base factory as a dedicated module focused on shared configuration: repo
settings, hooks, and generic helpers.

```elixir
# test/support/factory.ex
defmodule MyApp.Factory do
  use FactoryMan, repo: MyApp.Repo

  # Shared hooks
  def after_insert_handler(%_{} = struct),
    do: Ecto.reset_fields(struct, struct.__struct__.__schema__(:associations))

  # Generic helpers used across multiple child factories
  def generate_email(name), do: "#{name}@example.com"
end
```

Child factory modules extend the base and mirror your application's context structure:

```
test/support/
  factory.ex                    # Base factory (config, hooks, shared helpers)
  factory/
    accounts.ex                 # MyApp.Factory.Accounts (extends MyApp.Factory)
    blog.ex                     # MyApp.Factory.Blog (extends MyApp.Factory)
    blog/comments.ex            # MyApp.Factory.Blog.Comments (extends MyApp.Factory)
```

`test/support/factory/accounts.ex`

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

  alias MyApp.Accounts.User
  alias MyApp.Factory

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

    Map.merge(base_params, params)
  end
end
```

Why this structure?

- The base factory is a clear, single-purpose module. It is easy to find, and easy to understand
- Child factories map to application contexts, so you always know where to look
- The `extends:` relationship is obvious and consistent
- New team members (and LLM agents) can follow the pattern without guessing

### When to Use Params vs Struct vs Insert

- `build_*_params` - For testing changesets, passing to functions that expect maps, or when no
struct shape is needed.

- `build_*_struct` - For setting association fields on other structs being built in memory. Use
this when the record does not need to exist in the database.

- `insert_*!` - When a foreign key constraint requires the record to exist in the database, or
when the test itself queries the database for this record.

- Lazy evaluation (0-arity) - When a default value is expensive to compute or depends on runtime
state. This avoids eagerly inserting records that may not be needed.

- Lazy evaluation (1-arity) - When a field's default depends on another field in the same factory.
The function receives the parent map at build time.

A common mistake when building factories is inserting records into the database when a plain
struct would suffice. If the only reason to insert is to get an ID for a foreign key, consider
whether the test actually needs that constraint enforced. If not, a struct with a generated ID is
simpler and faster.

## Direct Struct Factories (`params?: false`)

For complex factories that need full control over struct construction, set `params?: false`.
The factory body returns a struct directly, and no `build_*_params` functions are generated:

```elixir
deffactory invoice(params \\ %{}), struct: Invoice, params?: false 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 `build_invoice_struct/0,1`, `insert_invoice!/0,1,2`, and list variants, but
**not** `build_invoice_params`.

`params?: false` can also be set at the module level via `use FactoryMan, params?: false`.

## Documentation

Full documentation is available in [the `FactoryMan` module](https://hexdocs.pm/factory_man/FactoryMan.html).