Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Elixir Ecto testing factories combined with StreamData generators.
defmodule MyApp.Factory do
use ExUnitProperties
alias MyApp.{Repo, User, Comment}
### Generators
def generator(:user) do
gen all name <- string(:alphanumeric, min_length: 2),
email <- generator(:email),
age <- integer(10..130) do
%User{name: name, email: email, age: age}
end
end
def generator(:comment) do
gen all body <- string(:alphanumeric, min_length: 10),
user <- generator(:user) do
%Comment{body: body, user: user}
end
end
def generator(:user_with_comments) do
gen all user <- generator(:user),
comments <-
list_of(
# modifying the comment generator to return a comment without a user to prevent Ecto association error
bind(generator(:comment), fn comment ->
constant(%{comment | user: nil})
end),
length: 1..3
) do
%{user | comments: comments}
end
end
## helper generators
def generator(:email) do
gen all local <- string(:alphanumeric, min_length: 1),
domain <- string(:alphanumeric, min_length: 1) do
local <> to_string(System.unique_integer()) <> "@" <> domain
end
end
### Factory API
def build(generator_name, attributes \\ []) do
1 |> build_list(generator_name, attributes) |> List.first()
end
def build_list(count, generator_name, attributes \\ []) do
generator_name
|> apply_attrs_to_generator(attributes)
|> Enum.take(count)
end
def insert(generator_name, attributes \\ []) do
1 |> insert_list(generator_name, attributes) |> List.first()
end
def insert_list(count, generator_name, attributes \\ []) do
generator_name
|> apply_attrs_to_generator(attributes)
|> Stream.map(&Repo.insert!/1)
|> Enum.take(count)
end
defp apply_attrs_to_generator(generator_name, attributes) do
generator_name
|> generator
|> Stream.map(&struct(&1, attributes))
end
end
defmodule MyApp.FactoryTest do
use MyApp.DataCase
use ExUnitProperties
import MyApp.Factory # better to import it in MyApp.DataCase
property "generate a user with comments" do
check all user <- generator(:user_with_comments) do
assert length(user.comments) >= 1
# do something with %User{}
end
end
describe "generators can be used as factories in example-based tests" do
test "build/2" do
assert comment = %MyApp.Comment{} = build(:comment)
assert comment.user
end
test "build_list/3" do
users = build_list(3, :user, name: "Andrea")
assert Enum.all?(users, &(&1.name == "Andrea"))
end
test "insert/2" do
user = insert(:user, name: "Michał")
comment = insert(:comment, user: user)
assert comment.id
assert comment.user == user
end
test "insert_list/3" do
users = insert_list(5, :user_with_comments)
assert users == Enum.uniq_by(users, fn user -> user.email end)
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.