Factori
Test data without boilerplate. Always in-sync with your database schema.
defmodule MyAppTest.Factory do
use Factori, repo: MyApp.Repo, mappings: [Factori.Mapping.Faker, Factori.Mapping.Enum]
end
user = MyAppTest.Factory.insert("users")
user.first_name # => "Lorem"
user.last_name # => "Ipsum"
Installation
In mix.exs
, add the factori dependency:
def deps do
[
{:factori, "~> 0.0.2"},
]
end
Overview
Define your Factory
module with the repo (typically in test/support
).
defmodule MyAppTest.Factory do
use Factori, repo: MyApp.Repo, mappings: [Factori.Mapping.Faker]
end
Initialize the module by checking out the Repo
and boostraping the Factory
.
This is typically done in data_case.ex
.
setup_all do
:ok = Ecto.Adapters.SQL.Sandbox.checkout(Repo)
Ecto.Adapters.SQL.Sandbox.mode(Repo, {:shared, self()})
MyApp.Factory.bootstrap()
:ok
end
Usage
In a test case, just use your Factory
module by referencing the table name
test "insert user" do
user = Factory.insert("users")
assert user.id
end
Overrides
test "insert user with overrides" do
user = Factory.insert("users", name: "Test")
assert user.name === "Test"
end
Mappings
Mappings are modules or functions used to map data to columns. factori
ships with a Faker integration that insert valid data from the type of the column. You can add your own mapper before Faker to override the data mapping:
defmodule MyAppTest.MappingCustom do
@behaviour Factori.Mapping
def match(%{name: :name}), do: "bar"
end
defmodule MyAppTest.Factory do
use Factori,
repo: MyApp.Repo,
mappings: [fn %{name: :name} -> "foo" end, MappingCustom, Factori.Mapping.Faker]
end
test "mappings" do
user = Factory.insert("users")
assert user.name === "foo"
end
Mappings also supports transforming data. This can be useful when we want random data but with a bit more control before inserting into the database:
In the example, the custom module does not implement the mapping, so the Faker one is taken. Then, the transform/2
is called to alter the data.
defmodule MyAppTest.Transform do
@behaviour Factori.Mapping
def transform(%{name: :password}, value), do: Bcrypt.hash_pwd_salt(value)
end
defmodule MyAppTest.Factory do
use Factori,
repo: MyApp.Repo,
mappings: [Transform, Factori.Mapping.Faker]
end
test "transforms" do
user = Factory.insert("users", password: "test123")
assert user.password === "$2b$12$3.EX0EHSwjNewmD18Ir5A.brKyJh3.DCKzLjX96wCwovzie2I1wcW"
end
The first module to implement a matching match
function will be taken, but the transform
is called on every items in mappings
options.
Variants
Instead of using string to reference the "raw" table names, you can use named variants:
defmodule MyAppTest.Factory do
use Factori,
repo: MyApp.Repo,
mappings: [Factori.Mapping.Faker],
variants: [{:user, "users"}]
end
MyAppTest.Factory.insert(:user)
MyAppTest.Factory.insert(:user, name: "Test")
Variants can also include overrides:
defmodule MyAppTest.Factory do
use Factori,
repo: MyApp.Repo,
mappings: [Factori.Mapping.Faker],
variants: [{:user, "users", name: "Test"}]
end
test "insert user with overrides" do
user = Factory.insert(:user)
assert user.name === "Test"
user = Factory.insert(:user, name: "123")
assert user.name === "123"
end
Ecto and structs
defmodule MyApp.User do
use Ecto.Schema
schema "users" do
field(:name, :string)
field(:admin, :boolean)
end
end
defmodule MyAppTest.Factory do
use Factori,
repo: MyApp.Repo,
mappings: [Factori.Mapping.Faker],
variants: [{:user, MyApp.User}, {:admin, MyApp.User, admin: true}]
end
test "insert ecto schema" do
user = Factory.insert(:user)
assert user.name
admin = Factory.insert(:admin)
assert admin.admin
end