Skip to content

Stubs: Simple, dependable, easy to reason about.

Notifications You must be signed in to change notification settings

daveshah/stubby

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

32 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Stubby

Simple, dependable, easy to reason about.

Why Stubby?

Quick answer: I'm currently working in an Elixir 1.3 app and Mox only supports 1.5😆

Long answer: When I'm in a functional language (like Elixir), I prefer stubs over mocks. I think sometimes people get these terms confused. I've seen people scratch their head when I've mentioned stubs, mocks, spy's, and why I prefer one over the other in some cases. If you're not familiar with the difference, check out Martin Fowler's article - Mock's aren't Stubs

I was happy when José Valim posted his opinions on Mocks and Explicit Contracts and liked the approach he showed with the controller, injecting the controllers dependency on the twitter API. This sets up a nice boundary and helps in thinking about de-coupling concerns and allows one to test the controller and api independently. In the example José has, he's able to test out the happy path by injecting a stub module during tests. The problem here is that, this only covers the happy path. What if calling this module yields an error, or there are additional cases from the output of the function that the controller should account for.

This is where stubbing (and Stubby) helps :)

Why Not Something Else?

I think constraints are a good thing when it comes to design (so long as you understand how constraints can impact design and meet your needs for your intended design). That said, limiting stubby to stubbing only imposes a set of constraints.

I'm also okay with ad-hock mocks. The library I like the most (mox) doesn't support ad-hoc mocks and requires that each mock adheres to the contract specified by a behaviour. I think this is great, but I'm also okay with not specifying a behaviour all of the time. Stubby allows you to create stubs without a beahviour 👍

If you're looking for more or think this isn't for you, checkout mox.

Some recommendations

Keep in mind that, when stubbing (and mocking), you're replacing the real implementation with a fake implementation. That said, it's best to ensure that you still have a good testing strategy in place to run your tests against codepaths that traverse the entire system using real implentations. These need not be the bulk of your tests and don't necessarilly have to be run as frequent as your other tests, but do not neglect these tests. It's all about finding a balance and understanding the what, why, and how your testing what you're testing.

Installation

Stubby is on Hex so just add Stubby to your list of dependencies in mix.exs:

def deps do
  [
    {:stubby, "~> 0.3.0", only: :test}
  ]
end

Usage

If you haven't yet, please read Mocks and Explicit Contracts

Using Stubby follows similiar patterns found in this post.

# Both your 'real' API and Stub API will implement this behaviour.
defmodule Api do
  @callback all() :: {:ok, term} | {:error, String.t}
  @callback get(term) :: {:ok, term} | {:error, String.t}
  ...
end

Use Stubby within a stub module, specifying which Behaviours you intend on stubbing

# This stub API can be set in your config/test.exs
defmodule MyApp.StubApi do
  use Stubby, for: [Api] 
  # NOTE: Multiple behaviours can be used with `use Stubby, for: [Behaviour1, Behaviour2, ...]`
end

# This is the API module that will be set in config/prod.exs
defmodule MyApp.RealApi do
  @behaviour Api
  ...
end

With a plain-old-module

# This stub API can be set in your config/test.exs or defined in your test
defmodule MyApp.StubModule do
  use Stubby, module: [SomeRealModule] 
end

Inject your controller

# In config/test.exs
config :my_app, :api, MyApp.StubApi

# In config/prod.exs
config :my_app, :api, MyApp.RealApi
defmodule MyAppWeb.MyController do
  use MyAppWeb :controller
  
  # 'Inject' your API behaviour 
  @api Application.get_env(:my_app, :api)
  
  ...
end

or inject collaborating modules:

defmodule MyApp.AwesomeContext do

  def get_all_the_awesome(query, api \\ MyApp.RealApi) do
    api.get(query)
    |> some_other_stuff(:that_is_awesome)
  end
end

Test with Stubby!

defmodule MyAppWeb.MyControllerTest do
  use MyAppWeb.ConnCase
  
  setup do
    # Call setup prior to stubbing
    StubApi.setup
  end
  
  test "a failing API call" do
    # Stub away!
    StubApi.stub(:all, fn -> {:error, \_(ツ)_/¯"} end)
    
    response = get conn, "/"
    
    assert json_response(response, 502)
  end
end
defmodule MyApp.AwesomeContextTest do
  use ExUnit.Case
  alias MyApp.AwesomeContext
  
  setup do
    # Call setup prior to stubbing
    StubApi.setup
    :ok
  end
  
  test "a failing API call" do
    # Stub away!
    StubApi.stub(:all, fn -> {:error, \_(ツ)_/¯"} end)
    
    assert {:error, "👎" } = AwesomeContext.get_all_the_awesome("👍", StubApi)
  end
end 

Once you call setup/0 on your Stub module, Stubby will generate functions that match the name and arity of what you've defined in your behaviours. You can stub out these functions by by calling stub/2.

The stub/2 function takes, as its first argument a name of the function you're stubbing and as it's second argument, an anonymous stub function. Note: Please ensure the stub function you pass matches the arity of the function you're stubbing As an example, if I were stubbing a get/1 function, this would look something like this:

StubApi.stub(:get, fn _unused -> "some output" end)

License

Copyright 2018 Dave Shah

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

  http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

About

Stubs: Simple, dependable, easy to reason about.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages