Packages

Elixir dependency registry for better testing and cleaner code

Current section

Files

Jump to
pact lib pact.ex
Raw

lib/pact.ex

defmodule Pact do
@moduledoc """
A module for managing dependencies in your application. You can set, get, and
override dependencies globally or per-pid.
## Example
```
Pact.start
Pact.put(:http, HTTPoison)
Pact.get(:http).get("https://google.com")
# You can also override per module
Pact.override(self, :http, FakeHTTP)
spawn(fn ->
Pact.get(:http).get("https://google.com") # Calls HTTPoison
end)
Pact.get(:http).get("https://google.com") # Calls FakeHTTP
```
"""
use GenServer
@doc """
Replace the given `name` for `pid` with the given expression. This will
generate a new module with the given methods.
## Example
```
import Pact
Pact.put(:enum, Enum)
Pact.replace self, :enum do
def map(_map, _fn) do
[1, 2, 3]
end
end
```
So now if you call `Pact.get(:enum).map(%{}, fn -> end)` it will return
`[1, 2, 3]`.
"""
defmacro replace(pid, name, expression) do
body = Keyword.get(expression, :do)
uid = :base64.encode(:crypto.strong_rand_bytes(5))
module_name = String.to_atom("Pact.Fakes.#{name}.#{uid}")
module = Module.create(module_name, body, Macro.Env.location(__ENV__))
quote do
Pact.override(unquote(pid), unquote(name), unquote(module_name))
end
end
def start(initial_modules\\ %{}) do
modules = %{modules: initial_modules, overrides: %{}}
GenServer.start(__MODULE__, modules, name: __MODULE__)
end
@doc "Gets the dependency with `name`"
def get(name) do
name = to_string(name)
GenServer.call(__MODULE__, {:get, name})
end
@doc "Assign `module` to the key `name`"
def put(name, module) do
name = to_string(name)
GenServer.cast(__MODULE__, {:put, name, module})
end
@doc "Override all calls to `name` in `pid` with `module`"
def override(pid, name, module) do
name = to_string(name)
GenServer.cast(__MODULE__, {:override, pid, name, module})
end
@doc "Remove override from process"
def remove_override(pid, name) do
name = to_string(name)
GenServer.cast(__MODULE__, {:remove_override, pid, name})
end
@doc "Stop Pact"
def stop do
GenServer.call(__MODULE__, :stop)
end
# GenServer
def init(container) do
{:ok, container}
end
def handle_cast({:put, name, module}, container) do
modules =
container.modules
|> Map.put(name, module)
{:noreply, %{container | modules: modules}}
end
def handle_cast({:override, pid, name, module}, container) do
override =
Map.get(container.overrides, pid, %{})
|> Map.put(name, module)
overrides = Map.put(container.overrides, pid, override)
{:noreply, %{container | overrides: overrides}}
end
def handle_cast({:remove_override, pid, name}, container) do
override =
Map.get(container.overrides, pid, %{})
|> Map.delete(name)
if Map.size(override) == 0 do
overrides = Map.delete(container.overrides, pid)
else
overrides = Map.put(container.overrides, pid, override)
end
{:noreply, %{container | overrides: overrides}}
end
def handle_call({:get, name}, {pid, _ref}, container) do
override = get_in(container.overrides, [pid, name])
if override do
module = override
else
module = Map.get(container.modules, name)
end
{:reply, module, container}
end
def handle_call(:stop, _from, container) do
{:stop, :normal, :ok, container}
end
end