Stubr v1.4.0
A large update has been made to Stubr including the introduction of spies and a large collection of functions like called_with?
, called_once?
, returns?
, etc… The full documentation is available in hexdocs.
About Stubr
Stubr is a set of functions helping people to create stubs and spies in Elixir.
In Elixir, you should aim to write pure functions. However, sometimes you need to write functions that post to external API’s or functions that depend on the current time. Since these actions can lead to side effects, they can make it harder to unit test your system.
Stubr solves this problem by taking cues from mocks and explicit contracts. It provides a set of functions that help people create “mocks as nouns” and not “mocks as verbs”:
iex> stub = Stubr.stub!([foo: fn _ -> :ok end], call_info: true)
iex> stub.foo(1)
:ok
iex> stub |> Stubr.called_once?(:foo)
true
iex> spy = Stubr.spy!(Float)
iex> spy.ceil(1.5)
iex> spy |> Stubr.called_with?(:ceil, [1.5])
true
iex> spy |> Stubr.called_twice?(:ceil)
false
Examples
Stubs
Stubs can be created using stub!/1
and stub!/2
:
iex> stub = Stubr.stub!([foo: fn -> :bar end])
iex> stub.foo()
:bar
iex> stub = Stubr.stub!([foo: fn _ -> :bar end], call_info: true)
iex> stub.foo(:baz)
iex> stub |> Stubr.called_with?(:foo, [:baz])
true
The input to these functions is a keyword list of function names
(expressed as atoms) and their implementations (expressed as
anonymous functions):
[function_name: (...) -> any()]
Additionally, takes an optional keyword list to configure the stub.
Options
The options available to stub!/2
are:
-
:module
- when set, if the module does not contain a function
defined in the keyword list, then raises anUndefinedFunctionError
-
:auto_stub
- when true and a module has been set, if there
is not a matching function, then defers to the module function
(defaults tofalse
) -
behaviour
- when set, raises a warning if the stub does not
implement the behaviour -
call_info
- when set, if a function is called, records the input
and the output to the function. Accessed by calling
Stubr.call_info(stub, :function_name)
(defaults tofalse
)
Spies
Spies can be created using spy!/1
:
iex> spy = Stubr.spy!(Float)
iex> spy.ceil(1.5)
iex> spy |> Stubr.called_with?(:ceil, [1.5])
true
iex> spy |> Stubr.called_twice?(:ceil)
false
Random numbers
It is easy to use Stubr.stub!
to set up a stub for the uniform/1
function in the :rand
module. Note, there is no need to explicitly set the module option, it is just used to make sure the uniform/1
function exists in the :rand
module.
test "create a stub of the :rand.uniform/1 function" do
rand_stub = Stubr.stub!([uniform: fn _ -> 1 end], module: :rand)
assert rand_stub.uniform(1) == 1
assert rand_stub.uniform(2) == 1
assert rand_stub.uniform(3) == 1
assert rand_stub.uniform(4) == 1
assert rand_stub.uniform(5) == 1
assert rand_stub.uniform(6) == 1
end
Timex
As above, we can use Stubr.stub!
to stub the Timex.now/0
function in the Timex
module. However, we also want the stub to defer to the original functionality of the Timex.before?/2
function. To do this, we just set the module
option to Timex
and the auto_stub
option to true
.
test "create a stub of Timex.now/0 and defer on all other functions" do
fixed_time = Timex.to_datetime({2999, 12, 30})
timex_stub = Stubr.stub!([now: fn -> fixed_time end], module: Timex, auto_stub: true)
assert timex_stub.now == fixed_time
assert timex_stub.before?(fixed_time, timex_stub.shift(fixed_time, days: 1))
end
HTTPoison
In this example, we create stubs of the functions get
and post
in the HTTPoison
module that return different values dependant on their inputs:
setup_all do
http_poison_stub = Stubr.stub!([
get: fn("www.google.com") -> {:ok, %HTTPoison.Response{body: "search", status_code: 200}} end,
get: fn("www.nasa.com") -> {:ok, %HTTPoison.Response{status_code: 401}} end,
post: fn("www.nasa.com", _) -> {:error, %HTTPoison.Error{reason: :econnrefused}} end
], module: HTTPoison)
[stub: http_poison_stub]
end
test "create a stub of HTTPoison.get/1", context do
{:ok, google_response} = context[:stub].get("www.google.com")
{:ok, nasa_response} = context[:stub].get("www.nasa.com")
assert google_response.body == "search"
assert google_response.status_code == 200
assert nasa_response.status_code == 401
end
test "create a stub of HTTPoison.post/2", context do
{:error, error} = context[:stub].post("www.nasa.com", "any content")
assert error.reason == :econnrefused
end