Stubr - Behaviours and Contracts

Stubr now has behaviours as an option:

In José Valim writes about finding “proper contracts and proper boundaries between the components” in your system. This is something Stubr can help you with to accomplish a “behaviour first” version of TDD.

For example, let’s say you want to call an internal API and you decide to use an adapter to wrap it up. However, you haven’t built the adapter yet, so you stub it out as an injected dependency without worrying about the implementation details.

So we might have something like this:

adapter_stub = Stubr.stub([
  {:get, fn("url") -> {:ok, "result"} end},
  {:put, fn("url", "data") -> {:ok} end}

# calls the get and put functions on the adapter stub

Immediately, this suggests that the adapter needs a behaviour that looks something like this:

defmodule AdapterBehaviour do
  @callback get(url :: String.t) :: {:ok, String.t}
  @callback put(url :: String.t, data :: String.t) :: {:ok}

So we can “clip” this on to the stub using the behaviour option:

adapter_stub = Stubr.stub([
  {:get, fn("url") -> {:ok, "result"} end},
  {:put, fn("url", "data") -> {:ok} end}
], behaviour: AdapterBehaviour)

If the generated stub does not implement the behaviour, then it throws the usual compiler warning.

After we’ve completed all our tests, we can now implement the adapter and ensure it implements the AdapterBehaviour:

defmodule Adapter do
  @behaviour AdapterBehaviour

  def get(url), do: #call the internal API
  def put(url, data), do: #call the internal API

The good thing about this is:

If you change the Adapter, then you should change the AdapterBehaviour. Since this will cause a compile time warning for the stub, it will encourage you to fix it, making your stubs and unit tests much less brittle.

Stubr also supports:

  • it is not a “mock” framework
  • provides canned answers to calls made during a test
  • easy to create stubs
  • makes sure the module you stub HAS the function you want to stub
  • stubs as many functions and patterns as you want
  • works without an explicit module. You set it up how you want
  • lets you do asynchronous tests
  • won’t redefine your modules!
  • has ZERO dependencies
  • records call information
  • auto stubs modules

Find Stubr on

1 Like