Skip to content

Instantly share code, notes, and snippets.

@ibarchenkov
Created January 31, 2019 21:09
Show Gist options
  • Star 7 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save ibarchenkov/b5982e42e660fe33eb023c164b1424d3 to your computer and use it in GitHub Desktop.
Save ibarchenkov/b5982e42e660fe33eb023c164b1424d3 to your computer and use it in GitHub Desktop.
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