adamu
Using Mox to mock DateTime used during init
I’m trying to use Mox to test a fix to a DateTime bug in my app.
The bug was that I was subtracting 1 from the current hour, which was failing at 00h.
I have a GenServer managing access tokens. It sends a message to itself every hour (using Process.send_after) to cleanup expired tokens. Part of this logic uses DateTime to calculate when the next cleanup should be scheduled. It schedules the first cleanup during init().
I want to test that the cleanup code works correctly at 00h, so my first thought was to mock DateTime and fix the date. DateTime doesn’t implement a behaviour, so I wrote my own behaviour + implementation that wraps the subset of DateTime functions I’m using.
The problem I’m having is how to mock this properly. I’m using Mox, and setting up a mock in test_helper.exs. However, because the Genserver uses the DateTime in init(), it tries to call the mock before I can define any expectations.
I tried adding Mox.stub_with at compile time in support/mocks.ex, to just forward to the real implementation for now, however then I get an error that Mox.Server is not started.
Is it possible to use Mox to mock a module when that module is used during application initialization?
Here’s the startup error using stub_with from the tests:
** (Mix) Could not start application my_app: MyApp.Application.start(:normal, []) returned an error: shutdown: failed to start child: MyApp.TokenServer
** (EXIT) an exception was raised:
** (Mox.UnexpectedCallError) no expectation defined for MyApp.DateTimeMock.utc_now/0 in process #PID<0.259.0> with args []
(mox 0.5.2) lib/mox.ex:693: Mox.__dispatch__/4
(my_app 0.1.0) lib/my_app/token_server.ex:64: MyApp.TokenServer.schedule_cleanup/0
(my_app 0.1.0) lib/my_app/token_server.ex:13: MyApp.TokenServer.init/1
Here’s the error if I try to use stub_with at compile time:
== Compilation error in file test/support/mocks.ex ==
** (exit) exited in: GenServer.call(Mox.Server, {:add_expectation, #PID<0.172.0>, {MyApp.DateTimeMock, :utc_now, 0}, {0, [], &MyApp.DateTime.utc_now/0}}, 30000)
** (EXIT) no process: the process is not alive or there's no process currently associated with the given name, possibly because its application isn't started
(elixir 1.10.3) lib/gen_server.ex:1013: GenServer.call/3
lib/mox.ex:547: Mox.add_expectation!/4
lib/mox.ex:476: Mox.stub/3
lib/mox.ex:526: anonymous fn/4 in Mox.stub_with/2
Finally, a bigger question: Any suggestions for alternative ways to test time bugs like this?
Most Liked
RudManusachi
I like this idea by @benwilson512
Adzz
I personally wrap DateTime in a helper, so I can make methods like Date.greater_than?
BUT regardless if you mock the actual datetime module and use an expect in the test it will work
def init() do
..
MyApp.DateTimeUtils.utc_now()
...
end
Then set up mox as usual:
# test.exs
config(:my_app, date_time_module: DateTimeMock)
# somewhere:
Mox.defmock(DateTimeMock, for: DateTime.Behaviour)
# the behaviour
defmodule DateTime.Behaviour do
@callback utc_now :: DateTime.t()
end
# the Utils Module
defmodule MyApp.DateTimeUtils do
@behaviour DateTime.Behaviour
@impl true
def utc_now() do
date_time().utc_now()
end
defp date_time(), do: Application.get_env(:my_app, :date_time_module, DateTime)
end
ityonemo
I think what you want to do is run Mox.stub_with immediately after Application.ensure_all_started(:mox) in test_helpers.exs. IIRC the Application tree is started as part of ExUnit.start
If this doesn’t work, as kobrakai suggests, you can do a thing where your server isn’t started in the Application tree, gated on Mix.env == :test (just remember to make sure it’s a compile time gate and not a runtime instruction); and you can manually start the server in test_helpers.
Popular in Questions
Other popular topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #phoenix_html
- #iex
- #blog-post
- #graphql
- #genstage
- #ai
- #websockets
- #supervisor
- #advent-of-code
- #elixirconf-us
- #distillery
- #processes
- #forms
- #api
- #metaprogramming
- #security
- #performance








