Definject - Unobtrusive Dependency Injector for Elixir

Hello,

I just published version 1.1 of definject. It aims to make dependency injection unobtrusive.

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 (simplified for understanding)

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

  welcome_email(to: email)
  |> Map.get(deps, &Mailer.send/1, &Mailer.send/1).()
end

Then, you can inject mock functions and modules in tests.

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

  assert_receive :email_sent
end

definject does not require you to modify function arguments or body. Instead, you just need to replace def with definject . It also allows injecting different mocks to each function. It also does not limit using :async option as mocks are contained in each test function.

You can find definject here:

Thanks!

6 Likes

Hey,

What’s the difference between definject and mox?

Awesome, thank you @jechol :clap:

Any ideas how to get VSCode to highlight the definject statements properly? :laughing:

I suggest you link to Hexdocs somewhere at the top of the README.md, would be handy.

Typically with Mox you sub out the module name at compile time and at test time you bind in functions. This cleverly uses registry, $callers and $ancestors (I think) to make sure tests get isolated mocks.

With definject, the function is majorly fiddled with (basically rewritten) so that you don’t have to do that compile-time module substitution.

I would be worried that definject is more brittle and has more corner cases, but it’s the sort of thing where if it works for you it’ll work really well.

2 Likes

I added documentation section just now, although content is almost same with README.
But, I have no idea how to make it highlighted in VSCode.

If you need it be formatted like def, add
locals_without_parens: [definject: 1, definject: 2]

@ityonemo explained well.

You can read more at Why?