Current section
Files
Jump to
Current section
Files
lib/tock.ex
defmodule Tock do
@moduledoc """
Tock is a library for mocking remote function calls made by `Task.Supervisor`.
## Usage
When working in a distributed system `Task.Supervisor` provides a mechanism
for calling functions on a remote node.
{ MyRemoteTaskSupervisor, remote_node }
|> Task.Supervisor.async(MyRemoteModule, :remote_fun, [])
|> Task.await()
Tock allows you to easily mock a remote application. This eliminates the need
to mock your own code. Instead, mock the behavior of an application running on
a remote node.
use ExUnit.Case, async: true
test "invokes add on a remote node" do
MyRemoteTaskSupervisor
|> Tock.start()
|> Tock.expect(MyRemoteMod, :add, fn(x, y) -> x + y end)
assert { MyRemoteTaskSupervisor, node() }
|> Task.Supervisor.async(MyRemoteModule, :add, [2, 3])
|> Task.await() == 5
end
All expectations are defined based on the current process. This allows
multiple tests to run concurrently when using the same named
`Task.Supervisor`.
"""
@doc """
Expects `fun` on `module` with an arity defined by `code` to be invoked `n`
times.
When `expect/5` is invoked, any previously declared stub for the same module,
function and arity will be removed. This will ensure that a remote function
called more than `n` times will timeout. If a `stub/4` is invoked after
`expect/5` for the same `module`, `fun` and arity, the stub will be used after
all expectations are fulfilled.
## Examples
Expect `MyRemoteMod.add/2` to be called once:
expect(MyRemoteTaskSupervisor, MyRemoteMod, :add, fn(x, y) -> x + y end)
Expect `MyRemoteMod.add/2` to be called 5 times:
expect(MyRemoteTaskSupervisor, MyRemoteMod, :add, 5, fn(x, y) -> x + y end)
`expect/5` can also be invoked multiple times for the same `module`, `fun` and
arity allowing you to define different results on each call:
MyRemoteTaskSupervisor
|> expect(MyRemoteMod, :add, fn(x, y) -> x + y end)
|> expect(MyRemoteMod, :add, fn(x, y) -> x * y end)
"""
@spec expect(atom | pid, module, atom, non_neg_integer, fun) :: pid
def expect(tock, module, fun, n \\ 1, code) do
signature = { module, fun, :erlang.fun_info(code)[:arity] }
expects = { signature, List.duplicate({ :expect, code }, n) }
Tock.Server.put_expectation(tock, expects)
end
@doc """
Start a mock `Task.Supervisor`.
"""
@spec start(atom) :: pid
def start(name) do
case Tock.Supervisor.start_server([name: name]) do
{ :ok, pid } ->
Tock.Server.join(pid)
{ :error, { :already_started, pid } } ->
Tock.Server.join(pid)
end
end
@doc """
Allows `fun` on `module` with an arity defined by `code` to be invoked zero or
more times.
If expectations and stubs are defined for the same `module`, `fun` and arity
the stub is invoked after all expectations are fulfilled.
## Examples
Allow `MyRemoteMod` to be invoked zero or more times:
stub(MyRemoteTaskSupervisor, MyRemoteMod, :add, fn(x, y) -> x + y end)
`stub/4` will overwrite any previous calls to `stub/4`.
"""
@spec stub(atom | pid, module, atom, function) :: pid
def stub(tock, module, fun, code) do
signature = { module, fun, :erlang.fun_info(code)[:arity] }
expects = { signature, [{ :stub, code }] }
Tock.Server.put_expectation(tock, expects)
end
end