Building an api client mock and learning to love mocks-as-nouns
This post was originally published on the Flatiron Labs blog, The Flatiron School Technology Team's blog. Check out more awesome Flatiron Labs posts here.
Why We Need Mox
In a recent post, we talked about the age-old question: How can you test code that relies on external services, like an API? We don’t want slow-running tests that make web requests to external services, potentially impacting things like API rate limits, production data and the overall reliability of our test suite.
This consideration often leads us to reach for a test mock. However, we want to be careful of how we think about “mocking”. When we mock certain interactions by creating unique function stubs in a given test example, we establish a dangerous pattern. We couple the run of our tests to the behavior of a particular dependency, like an API client. We avoid defining shared behavior among our stubbed functions. We make it harder to iterate on our tests.
Instead, Jose Valim advocates that we change the way we think about test mocks; that we think about a mock as a noun, instead of a verb:
Mocks are simulated entities that mimic the behavior of real entities in controlled ways…I always consider “mock” to be a noun, never a verb.
This reconceptualization forces us to create mock entities that our app is configured to use in the test environment, rather than mocking function calls with canned responses in our test examples.
In our first attempt at applying this advice to tests for a GitHub API client app that we built in Elixir, we hand-rolled our own test server. Our test server acted as a stand-in for the GitHub API in our test environment and it implemented a controller that responded with the expected payloads under the given conditions. Our test server became our mock. As a result, all of the test examples that made calls to this mock API implicitly mocked (there’s that verb again) those interactions.
This obfuscated mocking had some serious drawbacks. In order to write new tests that relied on GitHub API interactions, a developer would have to be aware of the test controller and comb through the test controller code to find the payload that their code is expected to operate on in a given test example.
The existence of a GitHub API mock server also allowed us to avoid defining an explicit contract for the api client that we were testing. Since we never had to mock the api client itself, it wasn’t strictly necessary to define an explicit interface for the behavior of that client.
By leveraging the Mox library to define mocks for our api client, we were able to address both of these drawbacks. This resulted in easy-to-read and easy-to-iterate on tests that allow each developer to define their own expectations against the mock client. It also forced us to define an explicit set of behaviors for our api client, in order for Mox to mock it.
Keep reading to see how we did it.
Getting Started
First things first, We need to add the Mox dependency to the mix.exs
file of our GithubClient
app:
{:mox, "~> 0.5.0", only: :test}
Mocking our Github Api Client

Defining a Behaviour
In order to use Mox to create a mock of a given module, that module must be an Elixir behaviour. Behaviours allow us to define a specific API that a set of modules must adhere to. We need to turn the module we want to create a mock out of into a behavior so that Mox can use the behavior to build the mock.
The module responsible for talking to the GitHub API is GithubClient.ApiClient
. We want to write tests for areas of our code that use GithubClient.ApiClient
. So, we need to be able to define a mock for this module. Let’s take a look at our module:
Note: This is an abbreviated look at the api client module, only including a handful of functions for finding/creating GitHub orgs.
Our module usesHTTPoison
to enact web requests to the GitHub API.
Let’s define a behaviour that we can use in this module:
Now that we have our behaviour, we’ll tell our api client module to use it:
Now that our api client module implements a behavior, Mox can use that behavior to create a mock.
Defining a Mock
Telling Mox to define a mock for a given behavior is pretty straightforward. We’ll define our mock in test/support/mocks.exs
Note: In order for the test/support/mocks.exs
file to be compiled by our application, we need to add the following to our mix file:
Now that our mock behavior is defined, we can teach our application to use the mock in the test environment, instead of the real api client.
Let’s say we have the following function in our GithubClient
module that calls on GithubClient.ApiClient
:
Note: This function calls on some ApiClient
functions not included in our abbreviated ApiClient
module or behavior. We’ve excluded their definitions for brevity.
Our GithubClient
should be configured to grab the GithubClient.ApiClient
in the dev and production environments, and the ApiClientBehaviourMock
in the test environment.
In our config/dev.exs
:
config :github_client, api_client: GithubClient.ApiClient
And in our config/test.exs
config :github_client, api_client: ApiClientBehaviourMock
We’ll teach the GithubClient
module to grab the correctly configured api client from the application environment, instead of calling on ApiClient
directly:
Now GithubClient
will use the mock in the test environment, and we’re ready to define some expectations!
Setting Expectations Against The Mock
Let’s say we have the following test for the function above:
We already know that GithubClient
will call on our ApiClientBehaviourMock
when the tests run. So, we’ll need to set some expectations against this mock that will get our test passing. In order for this happy-path test to run, we’ll need to tell our mock to expect to receive certain input when find_or_create_org/3
is called, and to return some valid response.
Note: We won’t go into mocking the other functions called on our client here, instead we’ll just focus on this one example.
We expect ApiClientBehaviourMock
to receive the find_or_create_organization/3
function with any args and return the tuple, {:ok, @org}
. And that’s it!
Refactoring our Api Client to Mock HTTP Interactions
Defining a behavior for GithubClient.ApiClient
allowed us to create a mock we could use when testing code that calls on this api client module. But what about testing the api client itself? After all, this module implements a fair amount of its own logic. So, how can we test the logic in our api client module, but still create a mock to handle the actual HTTP interactions?
We’ll refactor GithubClient.ApiClient
to abstract away the code that actually makes web requests. By separating out the concerns of GitHub API-specific logic from the responsibility of enacting HTTP requests, we end up with cleaner code that we can easily mock in our test suite.
First, we’ll define a new module responsible for using HTTPoison
to make HTTP requests:
Next, we’ll remove HTTP-specific code from GithubClient.ApiClient
and teach it to use our new adapter module instead.
Our api client no longer directly uses HTTPoison
, it doesn’t know how to format request headers or request URLs and it doesn’t have to deal with any JSON processing. It is only in charge of kicking off requests to the appropriate GitHub API endpoint and formatting the response so that it can be consumed elsewhere in the application. Our refactored module is much cleaner and more adherent the the Single Responsibility Principle.
Mocking HTTP Interactions
Now that we have a separate GithubClient.HttpAdapter
module that we can mock, we’re ready to take a look at some GithubClient.ApiClient
tests.
Let’s say we have the following test:
When this test runs, GitHubClient.ApiClient
will call GithubClient.HttpAdapter.get/1
, which will in turn call on HTTPoison.Base
’s get/2
function. So we need to define a mock for our adapter’s behavior and teach GithubClient.ApiClient
to use the mock in the test environment, instead of calling on the adapter directly.
But wait, you might be thinking. We haven’t implemented a behaviour for our adapter module! Well, our adapter module usesHTTPoison.Base
which implements its own behaviour. So, we don’t need to define one! Instead, we will create a mock for the HTTPoison.Base
behaviour directly:
# test/support/mocks.exs
Mox.defmock(HttpMock, for: HTTPoison.Base)
And we’ll configure our app to use our adapter module in dev and our mock adapter in the test environment:
# config/dev.exs
config :github_client, http_adatper: GithubClient.HttpAdapter
# config/test.exs
config :github_client, http_adapter: HttpMock
Lastly, we’ll teach GithubClient.ApiClient
to grab the correct adapter from the application environment:
Now we’re ready to define expectations against our mock in our test example:
And that’s it! By identifying a division within our original api client, and separating out the GitHub API logic from the code that enacts HTTP requests, we were able to write clean, well-organized code that was easy to mock and test.
The next challenge we faced was configuring Mox to test code that executed asynchronously in a GenServer. Stay tuned for our upcoming post on using Mox with GenServers to learn more!