All Projects → jechol → definject

jechol / definject

Licence: other
Unobtrusive Dependency Injector for Elixir

Programming Languages

elixir
2628 projects
Nix
1067 projects

Projects that are alternatives of or similar to definject

CNeptune
CNeptune improve productivity & efficiency by urbanize .net module with meta-code to lay foundation for frameworks
Stars: ✭ 30 (-34.78%)
Mutual labels:  mock, di
stashbox
A lightweight, fast, and portable dependency injection framework for .NET-based solutions.
Stars: ✭ 120 (+160.87%)
Mutual labels:  dependency-injection, di
Rewiremock
The right way to mock dependencies in Node.js or webpack environment.
Stars: ✭ 399 (+767.39%)
Mutual labels:  mock, dependency-injection
Ray.di
Guice style dependency injection framework for PHP
Stars: ✭ 175 (+280.43%)
Mutual labels:  dependency-injection, di
awilix-express
Awilix helpers/middleware for Express
Stars: ✭ 100 (+117.39%)
Mutual labels:  dependency-injection, di
Ulfberht
🗡️ A small but powerful & opinionated DI library. Written in Kotlin, and powered by annotation processing.
Stars: ✭ 234 (+408.7%)
Mutual labels:  dependency-injection, di
DI-compiler
A Custom Transformer for Typescript that enables compile-time Dependency Injection
Stars: ✭ 62 (+34.78%)
Mutual labels:  dependency-injection, di
Deli
Deli is an easy-to-use Dependency Injection(DI).
Stars: ✭ 125 (+171.74%)
Mutual labels:  dependency-injection, di
trew
A fast and very lightweight dependency injection library for Java 8+
Stars: ✭ 34 (-26.09%)
Mutual labels:  dependency-injection, di
ts-mock-imports
Intuitive mocking library for Typescript class imports
Stars: ✭ 103 (+123.91%)
Mutual labels:  mock, dependency-injection
Dig
A reflection based dependency injection toolkit for Go.
Stars: ✭ 2,255 (+4802.17%)
Mutual labels:  dependency-injection, di
swift-di-explorations
Functional DI explorations in Swift
Stars: ✭ 28 (-39.13%)
Mutual labels:  dependency-injection, di
Hiboot
hiboot is a high performance web and cli application framework with dependency injection support
Stars: ✭ 150 (+226.09%)
Mutual labels:  dependency-injection, di
Typhoon
Powerful dependency injection for Objective-C ✨✨ (https://PILGRIM.PH is the pure Swift successor to Typhoon!!)✨✨
Stars: ✭ 2,711 (+5793.48%)
Mutual labels:  dependency-injection, di
Di
PSR-11 compatible DI container and injector
Stars: ✭ 141 (+206.52%)
Mutual labels:  dependency-injection, di
Service Pattern Go
Simple clean Go REST API architecture with dependency injection and mocking example, following SOLID principles.
Stars: ✭ 449 (+876.09%)
Mutual labels:  mock, dependency-injection
Di
psr/container implementation for humans
Stars: ✭ 69 (+50%)
Mutual labels:  dependency-injection, di
Container Ioc
Inversion of Control container & Dependency Injection for Javascript and Node.js apps powered by Typescript.
Stars: ✭ 89 (+93.48%)
Mutual labels:  dependency-injection, di
waiter
Dependency injection, Inversion of control container for rust with compile time binding.
Stars: ✭ 71 (+54.35%)
Mutual labels:  dependency-injection, di
vesselize
⛵ A JavaScript IoC container that works seamlessly with Vue.js and React.
Stars: ✭ 22 (-52.17%)
Mutual labels:  dependency-injection, di

mix test Hex version badge License badge

Unobtrusive Dependency Injector for Elixir

Why?

Let's say we want to test following function.

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

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

Here's one possible solution to replace Repo.get/2 and Mailer.send/1 with mocks:

def send_welcome_email(user_id, repo \\ Repo, mailer \\ Mailer) do
  %{email: email} = repo.get(User, user_id)

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

First, I believe that this approach is too obtrusive as it requires modifying the function body to make it testable. Second, with Mailer replaced with mailer, the compiler no longer check the existence of Mailer.send/1.

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

Installation

The package can be installed by adding definject to your list of dependencies in mix.exs:

def deps do
  [{:definject, "~> 1.2"}]
end

By default, definject is replaced with def in all but the test environment. Add the below configuration to enable in other environments.

config :definject, :enable, true

To format definject like def, add following to your .formatter.exs

locals_without_parens: [definject: 1, definject: 2]

Documentation

API documentation is available at https://hexdocs.pm/definject

Usage

use Definject

use Definject transforms def to accept a extra argument deps where dependent functions and modules can be injected.

use Definject

def 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} =
    Map.get(deps, &Repo.get/2,
      :erlang.make_fun(Map.get(deps, Repo, Repo), :get, 2)
    ).(User, user_id)

  welcome_email(to: email)
  |> Map.get(deps, &Mailer.send/1,
       :erlang.make_fun(Map.get(deps, Mailer, 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 and modules in tests.

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

  assert_receive :email_sent
end

Function calls raise if the deps includes redundant functions or modules. 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: "[email protected]"} end,
    &Repo.all/1 => fn _ -> [%User{email: "[email protected]"}] end, # Unused
    strict: false
  })
end

mock

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

import Definject

test "send_welcome_email with mock/1" do
  Accounts.send_welcome_email(
    100,
    mock(%{
      Repo => MockRepo,
      &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.

import Definject

import Definject instead of use Definject if you want to manually select functions to inject.

import Definject

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

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

License

This project is licensed under the MIT License - see the LICENSE file for details

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].