README.md

# Resolve

Dependency injection and resolution at compile time or runtime.

Resolve is designed for swapping out dependencies in multi-target systems,
like embedded / IoT devices, where different hardware may be available depending
on which target the firmware is running on, or where physical hardware may be
missing all together when running the firmware on the host for development and
testing. That being said, Resolve also works for the traditional case of unit
testing, as the test environment is essentially just another type of target.

Resolve can be used in conjunction with mocks for testing, as they each have
their own advantages. Resolve has the benefit of not being linked to a process,
which means Resolve can be helpful for testing GenServers or other code that
runs in a process your test doesn't have direct access to. Resolve also allows
you to create throw-away anonymous modules for unit tests, rather than creating
named mock modules or factories.

## Installation

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

```elixir
def deps do
  [
    {:resolve, "~> 0.2.0"}
  ]
end
```

## Usage

Include resolve in the module that requires dependency injection with
`use Resolve`. Any place in that module that might need a dependency injected
can then use `resolve(Module)` to allow another module to be injected. The
module passed to `resolve/1` will be used if another module isn't injected.

```elixir
defmodule MyInterface do
  use Resolve

  def some_command, do: resolve(__MODULE__).some_command
end 
```

### Configuration

Resolve can be configured in the project's `config.exs`.

**Opts**
- `compile` - `false` - Sets the mappings at compile time and doesn't start 
    the process that allows them to be modified at runtime. This method is
    more secure and more performant. Compiling is intended for production and
    runtime is intended for unit tests.
- `mappings` - `[]` - A two element tuple of the modules to map from and to:
    `{from, to}`

**Example**


```elixir
config :resolve,
  compile: true,
  mappings: [
    {OriginalModule, InjectedModule},
  ]
```

### Runtime

Dependencies can be injected at runtime with `inject/2`. This is intended for
unit testing, but not necessarily limited to it. Runtime mappings will be
less performant compared to compiled mappings, as each lookup goes through
a read-optimized ETS table.

```elixir
Resolve.inject(OriginalModule, InjectedModule)
```

Modules can also be defined directly in a block, which can be helpful if they
are only needed for certain tests.

```elixir
Resolve.inject(Port, quote do
  def open(_name, _opts), do: self()

  def close(_port), do: :ok

  def command(_port, _data), do: :ok
end)
```

### Reverting a mapping

If dependencies are resolved at runtime, any injected dependencies for a module
can be removed by calling `revert/1`. This removes any mappings for the module
from the lookup table.

```elixir
Resolve.revert(Module)
```

### Unit testing

It can be more convenient to revert all of the dependencies after each unit test
runs, rather than keeping track of and reverting individual dependencies. This
can be done with the `revert_all` function if it is placed in a test helper or
configuration file, depending on how your test suite works.

ESpec example:

```ex
# spec_helper.exs

ESpec.configure(fn config ->
  config.finally(fn _shared ->
    Resolve.revert_all
  end)
end)
```