Current section
Files
Jump to
Current section
Files
lib/pact.ex
defmodule Pact do
@moduledoc """
A module for managing dependecies in your applicaiton without having to
"inject" dependencies all the way down your aplication. Pact allows you to
set and get dependencies in your application code, and generate fakes and
replace modules in your tests.
To use Pact, define a module in your application that has `use Pact` in it,
and then call `start_link` on it to start registering your dependencies.
## Usage
```
defmodule MyApp.Pact do
use Pact
register "http", HTTPoison
end
MyApp.Pact.start_link
defmodule MyApp.Users do
def all do
MyApp.Pact.get("http").get("http://foobar.com/api/users")
end
end
```
Then in your tests you can use Pact to replace the module easily:
```
defmodule MyAppTest do
use ExUnit.Case
require MyApp.Pact
test "requests the corrent endpoint" do
fakeHTTP = MyApp.Pact.generate :http do
def get(url) do
send self(), {:called, url}
end
end
MyApp.Pact.replace "http", fakeHTTP do
MyApp.Users.all
end
assert_receive {:called, "http://foobar.com/api/users"}
end
end
```
## Functions / Macros
* `generate(name, block)` - Generates an anonymous module that's body is
block`.
* `replace(name, module, block)` - Replaces `name` with `module` in the given
`block` only.
* `register(name, module)` - Registers `name` as `module`.
* `get(name)` - Get registed module for `name`.
"""
defmacro __using__(_) do
quote do
import Pact
use GenServer
@modules %{}
@before_compile Pact
defmacro generate(name, do: block) do
string_name = to_string(name)
uid = :base64.encode(:crypto.strong_rand_bytes(5))
module_name = String.to_atom("#{__MODULE__}.Fakes.#{string_name}.#{uid}")
module = Module.create(module_name, block, Macro.Env.location(__ENV__))
quote do
unquote(module_name)
end
end
defmacro replace(name, module, do: block) do
quote do
existing_module = unquote(__MODULE__).get(unquote(name))
unquote(__MODULE__).register(unquote(name), unquote(module))
unquote(block)
unquote(__MODULE__).register(unquote(name), existing_module)
end
end
def register(name, module) do
GenServer.cast(__MODULE__, {:register, name, module})
end
def get(name) do
GenServer.call(__MODULE__, {:get, name})
end
# Genserver implementation
def init(container) do
{:ok, container}
end
def handle_cast({:register, name, module}, state) do
modules = Map.put(state.modules, name, module)
{:noreply, %{state | modules: modules}}
end
def handle_call({:get, name}, _from, state) do
module = get_in(state, [:modules, name])
{:reply, module, state}
end
end
end
@doc false
defmacro register(name, module) do
quote do
@modules Map.put(@modules, unquote(name), unquote(module))
end
end
defmacro __before_compile__(_env) do
quote do
def start_link do
GenServer.start_link(__MODULE__, %{modules: @modules}, name: __MODULE__)
end
end
end
end