blacksmith

Data generation framework for Elixir.

In testing, sometimes it’s useful to create records in the form of maps. Blacksmith makes it easy.

First, install Blacksmith:

Right now, you’ll use mix with a git dependency. In your mix.exs file, add the blacksmith dependency:

def deps do
  [{:blacksmith, git: "git://github.com/batate/blacksmith.git"}]
end

You will also have to add :blacksmith to your application list:

def application do
  [applications: applications(Mix.env)]
end

defp applications(:test), do: applications(:all) ++ [:blacksmith]
defp applications(_all),  do: [:logger]

Next, tell Blacksmith how to save one record, or many records:

defmodule do Blacksmith.Config
  def save(repo, model) do
    repo |> save( model )
  end

  def save_all(repo, list_of_models) do
    repo |> save_all( list_of_models )
  end
end

Next, perhaps in test_helper for convenience or somewhere in lib for speed, register each of your new models with Forge. Use Faker for fake values, and sequences to maintain unique values:

defmodule Forge do
  use Blacksmith
  register :user,
    name: Faker.Name.first_name,
    email: Sequence.next(:email, &"test#{&1}@example.com"),
    description: Faker.Lorem.sentence,
    roles: [],
    always_the_same: "string"

  # this will create a user with roles set to [:admin]
  register :admin,
    [prototype: user],
    roles: ["admin"]
end

Now you can create a user, generating all of the default values:

user = Forge.user

or a saved user, with the name attribute overridden, and a new attribute of favorite_language:

user = Forge.saved_user Models.User, name: "Will Override", favorite_language: "Elixir"

or a list of 5 users

user = Forge.user_list 5

or a saved list of 5 admins

admin = Forge.saved_admin_list repo, 5

Create a list using a few common data elements:

Forge.having survey_id: some_survey.id, author: Forge.user do
    question = Forge.question   # will share the same survey id and user from above
  end

Next release: allow nesting of having blocks.

Using with Ecto

Blacksmith can be used easily with a database persistance library such as Ecto.

defmodule User do
  use Ecto.Model

  schema "users" do
    field :name, :string
    field :email, :string
  end
end

The @save_one_function and @save_all_function attributes are used to delegate to your persistence layer. We delegate to Blacksmith.Config defined below. You’ll also notice that we added the __struct__ field to register :user, that’s because Ecto works with models built on structs instead of plain maps.

defmodule Forge do
  use Blacksmith

  @save_one_function &Blacksmith.Config.save/2
  @save_all_function &Blacksmith.Config.save_all/2

  register :user,
    __struct__: User,
    name: "John Henry",
    email: Sequence.next(:email, &"jh#{&1}@example.com")
end

Blacksmith.Config defines the callback functions that delegate to the Ecto repository for persistence.

defmodule Blacksmith.Config do
  def save(repo, map) do
    repo.insert(map)
  end

  def save_all(repo, list) do
    Enum.map(list, &repo.insert/1)
  end
end

Forge.saved_user(MyRepo) will generate a User model that have been inserted in the database backed by MyRepo.