Elixir Patterns for Testing with Mox

When I need mocks in my Elixir tests, I prefer to use the Mox library. Mox follows the principle of mocking-by-explicit contract. The mox readme explains it like this:

  1. No ad-hoc mocks. You can only create mocks based on behaviours
  2. No dynamic generation of modules during tests. Mocks are preferably defined in your test_helper.exs or in a setup_all block and not per test
  3. Concurrency support. Tests using the same mock can still use async: true
  4. Rely on pattern matching and function clauses for asserting on the input instead of complex expectation rules

I’ve found that this works very well with the overall design of the Elixir language. Elixir developers generally prefer explicitly combining functional pieces over any sort of magical hidden state. Some of the other mocking libraries fiddle with your function definitions behind the scenes. I’ve found that this makes test code harder to read and debug.

When you are using Mox, I believe that it helps if you follow certain patterns. I’ve worked on several projects using Mox at this point, and I’ve seen the pain that can come from not establishing those pattern up front. I will summarize my recommendations briefly here before showing some examples:

  1. You should define one facade module which both your application and test code call in almost all cases.
  2. The facade module should delegate all its work to either the true adapter or the mock adapter, depending on the environment.
  3. One piece of configuration should control which adapter is used, and that configuration should be wrapped up in a nice easy to call function.

To boil that down to only one sentence: your application code should not know or care that you are using Mox. If you find your application code caring that it could be mocked out in a test later, you need a new set of abstractions. In fact, most of your test code shouldn’t need to know about the mocks, except where it needs to set function call expectations.

What does the code to do this look like? Let’s say your application uses a weather API and you don’t want to use the real API in your test suite.

Let’s say this is the module your application already has for communicating with the weather API:

defmodule Weather do
  def current_weather(zip_code) do
      # makes a GET request to the API to ask for the current weather in a zip code
  end
end

We would define a behaviour for a weather adapter:

defmodule WeatherBehaviour do
  @callback current_weather(binary) :: map
end

We would have the Weather module adopt the behavior and we would rename it so that it can serve as our live api adapter. Later, we’ll tell our application to use the LiveWeather adapter by default.

defmodule LiveWeather
  @behaviour WeatherBehaviour
    
  def current_weather(zip_code) do
      # makes a GET request to the API to ask for the current weather in a zip code
  end
end

At this point, we need to create a new Weather module so that all of our application code doesn’t explode. Our new Weather module won’t do much. It will just delegate down to either LiveWeather, or the mock weather adapter as appropriate for the environment:

defmodule Weather do
  defdelegate current_weather(zip), to: WeatherApp.weather_adapter()
end

and we’ll need to define weather_adapter/0 in our application class:

defmodule WeatherApp do
  def weather_adapter do
      Application.get_env(:weather_app, :weather_api_adapter, LiveWeather)
  end
end

By default, the application will use LiveWeather. This is good for development and production. However, in the test environment, we need to tell the system to use the mock. In config/test.exs:

config :weather_app, weather_api_adapter: WeatherMock

And we need to actually define our mock! In test/test_helper.exs:

Mox.defmock(WeatherMock, for: WeatherBehaviour)

That’s all the setup. Notice that nothing outside of the Weather module and it’s configuration changed. None of our controllers or other contexts were disturbed in the process.

If we want to write a test for a function that uses our Weather API, we need to tell Mox what function calls to expect and what to return:

defmodule UserTest do
  # standard test boilerplate as before
    
  test "current_weather/1 gives the current weather for the user" do
    user = %User{zip_code: "19120"}
        
    WeatherMock
    |> expect(:current_weather, fn "19120" ->
          # I've heard it is always sunny there
          %{"description" => "clear"}
    end)
        
    assert %{"description" => "clear"} = User.current_weather(user)
  end
end

Categories:


30 of the Best People in Software Lost Their Jobs Yesterday

30 of the best people in software lost their jobs yesterday. And I lost my job too. Revelry Labs, where I worked for close to seven years (five of those as VP of Engineering), has downsized from around 70 people to about half that.

I’ve never met such a high-performing group of hyper-competent and flexible problem-solvers. These people are top-of-their-class Engineers, Designers, Product Managers, Marketing folks, Relationship Managers, and more. But ultimately, I think what sets these folks apart is their humanity, openness, good humor, and adaptability. I think any team would be lucky to have them.

I’m still a big believer in Revelry. The team that is remaining there is also full of amazing, hyper-competent, kind, and caring people. I think Revelry is one of the best agencies out there and one of the most underrated. I’ve learned so much from Gerard in the last seven years. I feel a whole slew of contradictory emotions about this right now, but I can say I’m a lot more sad than mad.

I joined as the 2nd Engineering hire. We grew the company from 4 people to almost 70. My role was growing the Engineering team from 2 to over 30. But I don’t care about the growth itself. What I care about is that we managed to grow 10x while remaining open, transparent, flat, democratic, and kind. It’s that at every turn, between any two options, we took the one that was moral, ethical, and humane.

None of these people lost their jobs through any fault of their own. Some of us put ourselves on the list because we refused to let anyone go unless we went first. Some people merely lost the die roll by not being attached to any billable work at the moment when the economy tanked.

I want to talk about a few of these people specifically. I haven’t had a chance to talk to everyone yet, and I didn’t want to mention anyone without express permission. Anyone missing from this list is missing for that reason. I’ll continue to update this list as I catch up with more folks:

John Hawkins is the person who taught me to respect and enjoy WordPress (a framework that I previously loathed) through his transfer of enthusiasm. I’ve seen him twist and bend the framework in ways I didn’t think possible in order to achieve amazing things on extremely short timelines. Need WordPress? John is your guy. You can reach him here.

Ben Rongey has the rare ability to be direct and say what everyone else is thinking, but in a way that puts everyone at ease and points them in the right direction. Even when he was a very new member of the team, he course-corrected me meaningfully multiple times. He’s also a hell of an Engineer who undersells himself. And he’s only going to get much better over the next few years of his career. Need someone who is not just a great Engineer, but can set you straight with kindness and professionalism? That’s Ben. Here is Ben on LinkedIn.

Mary Legendre was the head of our Quality Assurance and Test Engineering group. Her growth over the last five years has been unreal. She completely built and formalized our process for all things QA, both manual and automated. She designed and implemented our entire modern testing framework in Elixir and JavaScript including integration testing, headless browsers, mobile testing, and CI/CD pipelines. Everything. She’s also a natural leader with a style that is all about empathy and professionalism hand-in-hand. You should let Mary build out your QA department. Here is Mary on LinkedIn.

Josh Frank brought so many missing pieces to our puzzle when he joined. I’ve never known anyone who could better balance the demands of our customers, our customer’s customers, and our team against the ugly reality of budgets and timelines. He’s responsible for the excellence of our Product, Process, and Project Management approaches. Need an amazing Product mind? Josh is the one for you. He’s here on LinkedIn and here on Twitter.

Hauwa Aguillard is another one who really helped us to complete our equation. Hauwa is a UX expert. She’s the one who taught us to not just care about user data in the abstract but to make it real and part of every decision we made. She also clearly cares about community and teaching. Every time Hauwa goes to a conference, you know that she’s going to come back and teach the team everything she learned. And whenever she learns something on the job, she finds a way to share it with other UX practitioners in our community. Need someone who will make your UX program best in class? That’s Hauwa. You can find her on LinkedIn here.

Aline Adams is just a Swiss-army knife of competence. We put her through half a dozen different job descriptions in as many years and she was good at every single one of them. We did it because he knew she would handle it. Project Management, on software projects, or marketing campaigns, or whatever? Aline’s got you. Content development? Aline’s got you. Marketing? SEO? Aline’s got you. Etcetc? Aline’s got you. You can find Aline here.

I can also privately connect you with a lot more folks of this caliber, across all disciplines— Engineers, Designers, Growth, Operations, &c &c. You can reach me on Twitter or by email.

I can’t recommend these folks strongly enough. You should hire them. And you should probably do it soon before either Revelry snaps them back up or I figure out how to hire them myself.

Categories: