22
votes

When writing integration tests that depend on the current date/time, it is very handy to be able to freeze or travel to specific moment (like e.g. timecop for ruby)

Is there a way to achieve something similar in Elixir/Erlang?

I tried mocking Erlang built-ins :os.timestamp, :erlang.universaltime using meck library, however it fails with :cannot_mock_builtin.

In principle I could implement my own utility library than would enable easy mocking of current time and then use it everywhere instead of built-in methods; however, some libraries use built-ins, so this is not a viable option (e.g Ecto.Model.Timestamps, generating inserted_at and updated_at values)

1
Have you checked the recent changelog for Erlang 18? It has a TImeWarp feature and ferd has written about it in learnyousomeerlang. Erlang time warp documentation and ferd writings on the subject. No sure though they answer your question...Olinasc
@Olinasc The time warp feature is only related to reacting to changing system time and clocks in general, it does not allow you to change the time for tests only.Adam Lindberg

1 Answers

7
votes

Elixir

I would suggest you implement this via dependency injection. For example (using the new time API in Erlang 18):

defmodule Thing do
  def do_stuff(data), do: do_stuff(data, &:erlang.system_time/0)
  def do_stuff(data, time), do: {data, time.()}
end

In your tests, you can easily replace the time code:

defmodule ThingTest do
  use ExUnit.Case

  test "the time" do
    assert do_stuff("data", fn -> 123 end) == {"data", 123}
  end
end

Erlang

Here's the corresponding way to do this in Erlang:

-module(thing).
-export([do_stuff/1, do_stuff/2]).

do_stuff(Data) -> do_stuff(Data, fun erlang:system_time/0).

do_stuff(Data, Time) -> {Data, Time()}.

And the test:

-module(thing_tests).
-include_lib("eunit/include/eunit.hrl").

do_stuff_test() ->
    ?assertEqual({"data", 123}, do_stuff("data", fun() -> 123 end).