definject v0.4.2 Definject View Source

Link to this section Summary

Functions

definject transforms a function to accept a map where dependent functions can be injected.

If you don't need pattern matching in mock function, mock/1 can be used to reduce boilerplates.

Link to this section Functions

Link to this macro

definject(head, list)

View Source (macro)

definject transforms a function to accept a map where dependent functions can be injected.

import Definject

definject send_welcome_email(user_id) do
  %{email: email} = Repo.get(User, user_id)

  welcome_email(to: email)
  |> Mailer.send()
end

is expanded into

def send_welcome_email(user_id, deps \\ %{}) do
  %{email: email} = (deps[&Repo.get/2] || &Repo.get/2).(User, user_id)

  welcome_email(to: email)
  |> (deps[&Mailer.send/1] || &Mailer.send/1).()
end

Note that local function calls like welcome_email(to: email) are not expanded unless it is prepended with __MODULE__.

Now, you can inject mock functions in tests.

test "send_welcome_email" do
  Accounts.send_welcome_email(100, %{
    &Repo.get/2 => fn User, 100 -> %User{email: "mr.jechol@gmail.com"} end,
    &Mailer.send/1 => fn %Email{to: "mr.jechol@gmail.com", subject: "Welcome"} ->
      Process.send(self(), :email_sent)
    end
  })

  assert_receive :email_sent
end

definject raises if the passed map includes a function that's not called within the injected function. You can disable this by adding strict: false option.

test "send_welcome_email with strict: false" do
  Accounts.send_welcome_email(100, %{
    &Repo.get/2 => fn User, 100 -> %User{email: "mr.jechol@gmail.com"} end,
    &Repo.all/1 => fn _ -> [%User{email: "mr.jechol@gmail.com"}] end, # Unused
    strict: false,
  })
end

If you don't need pattern matching in mock function, mock/1 can be used to reduce boilerplates.

test "send_welcome_email with mock/1" do
  Accounts.send_welcome_email(
    100,
    mock(%{
      &Repo.get/2 => %User{email: "mr.jechol@gmail.com"},
      &Mailer.send/1 => Process.send(self(), :email_sent)
    })
  )

  assert_receive :email_sent
end

Note that Process.send(self(), :email_sent) is surrounded by fn _ -> end when expanded.