Introducing Mox to legacy code

I’m working on legacy code that does not use Mox.

I’ve had to write a new test that does DateTime testing. I mock out the utc_now() code and things seem to be working good for my test.

Problem is it breaks every other test that tests a function using DateTime.utc_now().
(Mox.UnexpectedCallError) no expectation defined for...

I’m new to Elixir and am trying to figure out ways to get my team using Mox with step-by-step without having to change all our testing code.

How can I introduce the use of a Mox mock to just one test? And then gradually update the other tests over time?

Mox is designed so that you need to pass the mock explicitly to the function instead of the real implementation. How does your function get access to your real DateTime module or mock?

I assume you are getting the modules from the config and you’re using this everywhere in your codebase. In this case simply use the module from the config only for the function you want to do a mocked test for, and continue using the real implementation directly for the rest of the codebase.

Thanks for your response! Here is some more info for you…

The setup for the function being called that ends up using either the real or mocked version of DateTime.utc_now() looks like this…

Setup the interface and real behavior/implementation.

defmodule MyApp.DateTime do
  @date_time_api Application.get_env(:my_app, :date_time_api, MyApp.DateTimeBehavior)

  def utc_now(calendar \\ Calendar.ISO) do
    @date_time_api.utc_now(calendar)
  end
end

defmodule MyApp.DateTimeApi do
  @callback utc_now(Calendar.calendar()) :: DateTime.t()
end

defmodule MyApp.DateTimeBehavior do
  @behaviour MyApp.DateTimeApi

  @impl MyApp.DateTimeApi
  def utc_now(calendar \\ Calendar.ISO) do
    DateTime.utc_now(calendar)
  end
end

And to be sure, my function under test calls: MyApp.DateTime.utc_now() and not DateTime.utc_now() directly.

At first I tried things like in the apps/myapp/config/test.exs config file

config :my_app, date_time_api: MyApp.DateTimeMock

and I also tried in the apps/myapp/test/test_helper.exs file

Mox.defmock(MyApp.DateTimeMock, for: MyApp.DateTimeApi)
Application.put_env(:my_app, :date_time_api, MyApp.DateTimeMock)

But this seemed to effect all tests in all apps under the umbrella app, and not just the app under test.

Because I’m dealing with a legacy system and don’t yet want to mock the behavior for every very test, I tried doing using setup_all() of my specific test file instead of putting this code in test_helper.exs. So in my test I have:

# I tried both async: true first and switched to async: false thinking that might help. but it didn't 
 use ExUnit.Case, async: false

  # Make sure mocks are verified when the test exits
  setup :verify_on_exit!

  setup_all do
    Mox.defmock(MyApp.DateTimeMock, for: MyApp.DateTimeApi)
    Application.put_env(:my_app, :date_time_api, MyApp.DateTimeMock)

    # revert mock back to original module
    on_exit(fn ->
      Application.get_env(:my_app, :date_time_api, MyApp.DateTimeBehavior)
    end)
  end 

This didn’t help either.

Any thoughts as to what I’m doing wrong? Any thoughts as to how I can apply a Mox mock to just a single test file, so I can introduce it gradually into the system?

Much appreciated!

1 Like