README.md

# Double
Double is a simple library to help build injectable dependencies for your tests.
It does NOT override behavior of existing modules or functions.

## Installation

The package can be installed as:

  1. Add `double` to your list of dependencies in `mix.exs`:

    ```elixir
    def deps do
      [{:double, "~> 0.2.5", only: :test}]
    end
    ```

## Usage

The first step is to make sure the function you want to test will have it's dependencies injected.
This library requires usage of a map or struct:

### With Map
```elixir
defmodule Example do
  @inject %{
    puts: &IO.puts/1,
    some_service: &SomeService.process/3,
  }

  def process(inject \\ @inject) do
    inject.puts.("It works without mocking libraries")
    inject.some_service.(1, 2, 3)
  end
end
```

### With Struct

Using a struct has the benefit of throwing an error when trying to allow a non-existent key, and
you may also re-use your struct if you share similar dependencies throughout your app.

```elixir
defmodule Example do
  defmodule Inject do
    defstruct puts: &IO.puts/1, some_service: &SomeService.process/3
  end

  def process(inject \\ %Inject{}) do
    inject.puts.("It works without mocking libraries")
    inject.some_service.(1, 2, 3)
  end
end

```

### Setting up your doubles

```elixir
defmodule ExampleTest do
  use ExUnit.Case
  import Double

  test "example interacts with things" do
    inject = double
    |> allow(:puts, with: {:any, 1}, returns: :ok) # {:any, x} will accept any values of arity x
    |> allow(:some_service, with: [1, 2, 3], returns: :ok) # strictly accepts these three arguments

    Example.process(inject)

    # now just use the built-in ExUnit methods assert_receive/refute_receive to verify things
    assert_receive({:puts, "It works without mocking libraries"})
    assert_receive({:some_service, 1, 2, 3})
  end
end
```

## Features

### Basics

```elixir
# no return value or arguments
double = double |> allow(:example)
double.example.() #nil

# only accept specific arguments
double = double |> allow(:example, with: ["hello world"])
double.example.("hello world") # nil

# setup return value
double = double |> allow(:example, with: ["hello world"], returns: :ok)
double.example.("hello world") # :ok

# accept any arguments of specific arity
double = double |> allow(:example, with: {:any, 2}, returns: :ok)
double.example.("hello", "world") # :ok

# stub as many functions as you want
double = double
|> allow(:example)
|> allow(:another_example)

# you can even add your own data or stubs to the map, it's just a normal map
double = double
|> Map.merge(%{some_value: "hello"})
|> allow(:example)
double.some_value # "hello"
double.example.() # nil
```

### Different return values for different arguments
```elixir
double = double
|> allow(:example, with: ["one"], returns: 1)
|> allow(:example, with: ["two"], returns: 2)
|> allow(:example, with: ["three"], returns: 3)

double.example.("one") # 1
double.example.("two") # 2
double.example.("three") # 3
```

### Multiple calls returning different values
```elixir
double = double
|> allow(:example, with: ["count"], returns: 1, returns: 2)

double.example.("count") # 1
double.example.("count") # 2
double.example.("count") # 2
```

### Struct key verification

```elixir
double = double(%MyStruct{})
|> allow(:example, with: ["hello"], returns: "world") # will error if :example is not a key in MyStruct.
```

### Exceptions

```elixir
double = double
|> allow(:example_with_error_type, raises: {RuntimeError, "kaboom!"})
|> allow(:example_with_message_only, raises: "kaboom!") # defaults to RuntimeError
```
### Nested Doubles
If you want to group some of your stubbed functions in a nested map, that works just like setting any other value in a map.
```elixir
double = double
|> allow(:example)
|> Map.put(:logger, double
  |> allow(:info, with: {any: 1}, returns: :ok)
  |> allow(:error, with: {any: 1}, returns: :ok)
  |> allow(:warn, with: {any: 1}, returns: :ok)
)
double.logger.info.("test") # :ok
```