Here is the way I did it with a behaviour:
1.) In the mix.exs
use elixirc_paths
:
def project do
[app: :coffee_fsm,
version: "0.1.0",
elixir: "~> 1.4",
elixirc_paths: elixirc_paths(Mix.env),
build_embedded: Mix.env == :prod,
start_permanent: Mix.env == :prod,
deps: deps()]
end
# Configuration for the OTP application
#
# Type "mix help compile.app" for more information
def application do
# Specify extra applications you'll use from Erlang/Elixir
[extra_applications: [:logger]]
end
defp elixirc_paths(:test), do: ["lib","test/support"]
defp elixirc_paths(_), do: ["lib"]
essentially this adds the “test/support” directory where the mock files are
2). in config/config.exs
:
case Mix.env do
:test ->
config :coffee_fsm, hw: HwMock
_ ->
config :coffee_fsm, hw: HwOutput
end
Essentially this will cause test\support\hw_mock.ex
to load for testing through @hw_impl
in lib\hw.ex
- otherwise lib\hw_output.ex
is loaded.
3). Meanwhile lib\hw.ex
looks like this:
defmodule Hw do
@hw_impl Application.fetch_env!(:coffee_fsm, :hw)
defmodule Behaviour do
@callback display(f,a) :: :ok when f: String.t(), a: [any()]
@callback return_change(n) :: :ok when n: non_neg_integer()
@callback drop_cup() :: :ok
@callback prepare(t) :: :ok when t: atom()
@callback reboot() :: :ok
end
@spec display(s,a) :: :ok when s: String.t(), a: [any()]
def display(str, args), do: @hw_impl.display(str, args)
@spec return_change(n) :: :ok when n: non_neg_integer()
def return_change(payment), do: @hw_impl.return_change(payment)
@spec drop_cup :: :ok
def drop_cup, do: @hw_impl.drop_cup()
@spec return_change(b) :: :ok when b: CoffeeFsm.beverage()
def prepare(type), do: @hw_impl.prepare(type)
@spec reboot :: :ok
def reboot, do: @hw_impl.reboot()
end
while test\support\hw_mock.ex
looks like this:
defmodule HwMock do
@behaviour Hw.Behaviour
use GenServer
defp forward_pending(pending, pid) when is_pid pid do
forward_to =
fn(request, _) ->
Kernel.send pid, request
:ok
end
pending
|> Enum.reverse()
|> Enum.reduce(:ok, forward_to)
end
def handle_cast(entry, {:none, pending}) do
{:noreply, {:none, [entry | pending]}}
end
def handle_cast(entry, {pid, pending}) do
forward_pending [entry | pending], pid
{:noreply, {pid, []}}
end
def handle_call({:forward, pid}, _from, {_, pending}) when is_pid pid do
forward_pending pending, pid
{:reply, :ok, {pid, []} }
end
def handle_call({:forward, _}, _from, {_, pending}) do
{:reply, :ok, {:none, pending} }
end
def handle_call(:clear, _from, {pid, _}) do
{:reply, :ok, {pid, []} }
end
defp log(entry) do
GenServer.cast __MODULE__, entry
end
def init(_args), do: {:ok, {:none, []}}
# public interface
def start_link, do: GenServer.start_link __MODULE__, [], name: __MODULE__
def clear(pid) do
GenServer.call __MODULE__, :clear
end
def forward(pid) do
GenServer.call __MODULE__, {:forward, pid}
end
# implementing "Hw"" behaviour
def display(str, args) do
log { :display, [str, args] }
:ok
end
def return_change(payment) do
log { :return_change, [payment] }
:ok
end
def drop_cup do
log { :drop_cup, [] }
:ok
end
def prepare(type) do
log { :prepare, [type] }
:ok
end
def reboot do
log { :reboot, [] }
:ok
end
end