Skip to content

Commit

Permalink
Add basic usage
Browse files Browse the repository at this point in the history
  • Loading branch information
Paulo Daniel Gonzalez committed Jan 6, 2022
1 parent a5e9175 commit 3dc8d7b
Showing 1 changed file with 86 additions and 0 deletions.
86 changes: 86 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,92 @@ end

Mox should be automatically started unless the `:applications` key is set inside `def application` in your `mix.exs`. In such cases, you need to [remove the `:applications` key in favor of `:extra_applications`](https://elixir-lang.org/blog/2017/01/05/elixir-v1-4-0-released/#application-inference) or call `Application.ensure_all_started(:mox)` in your `test/test_helper.exs`.

## Basic Usage

### 1) Add behaviour, defining the contract:
```
# lib/weather_behaviour.ex
defmodule WeatherBehaviour do
@callback get_weather(binary()) :: {:ok, map()} | {:error, binary()}
end
```

### 2) Add implementation for the behaviour
```
# lib/weather_impl.ex
defmodule WeatherImpl do
@moduledoc """
An implementation of a WeatherBehaviour
"""
@behaviour WeatherBehaviour
@impl WeatherBehaviour
def get_weather(city) when is_binary(city) do
# Here you could call an external api directly with an HTTP client or use a third
# party library that does that work for you. In this example we send a
# request using a `httpc` to get back some html, which we can process later.
:inets.start()
:ssl.start()
case :httpc.request(:get, {"https://www.google.com/search?q=weather+#{city}", []}, [], []) do
{:ok, {_, _, html_content}} -> {:ok, %{body: html_content}}
error -> {:error, "Error getting weather: #{inspect(error)}"}
end
end
end
```

### 3) Add a switch
This can pull from your `config/config.exs`, `config/test.exs`, or, you can have no config as shown below and rely on a default. We also add a function to a higher level abstraction that will call the correct implementation:
```
# bound.ex, the main context we chose to call this function from
defmodule Bound do
def get_weather(city) do
weather_impl().get_weather(city)
end
defp weather_impl() do
Application.get_env(:bound, :weather, WeatherImpl)
end
end
```

### 4) Define the mock so it is used during tests:
```
# in test/test_helper.exs
+Mox.defmock(WeatherBehaviourMock, for: WeatherBehaviour)
+Application.put_env(:bound, :weather, WeatherBehaviourMock)
ExUnit.start()
```

### 5) Create a test and use `expect` so we can assert on the arguments that are passed to the mock.
```
# test/bound_test.exs
defmodule BoundTest do
use ExUnit.Case
import Mox
setup :verify_on_exit!
describe "get_weather/1" do
test "fetches weather based on a location" do
expect(WeatherBehaviourMock, :get_weather, fn args ->
# here we can assert on the arguments that get passed to the function
assert args == "Chicago"
# here we decide what the mock returns
{:ok, %{body: "Some html with weather data"}}
end)
assert {:ok, _} = Bound.get_weather("Chicago")
end
end
end
```

## Enforcing consistency with behaviour typespecs

[Hammox](https://github.com/msz/hammox) is an enhanced version of Mox which automatically makes sure that calls to mocks match the typespecs defined in the behaviour. If you find this useful, see the [project homepage](https://github.com/msz/hammox).
Expand Down

0 comments on commit 3dc8d7b

Please sign in to comment.