23

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)

Adam Lindberg
  • 16,447
  • 6
  • 65
  • 85
jesenko
  • 1,273
  • 12
  • 18
  • 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](http://www.erlang.org/doc/apps/erts/time_correction.html) and [ferd writings on the subject](http://learnyousomeerlang.com/time). No sure though they answer your question... – Olinasc Oct 02 '15 at 14:04
  • @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 Oct 03 '15 at 10:45

1 Answers1

8

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).
Adam Lindberg
  • 16,447
  • 6
  • 65
  • 85
  • 3
    Thanks for the answer! However I am not sure how this approach would work in context of integration test, where _external_ libraries use `erlang:system_time` (e.g. `Ecto` for automatically setting timestamps on inserted/updated rows)... – jesenko Oct 03 '15 at 18:40
  • 2
    Yes, there you have no luck. There is no way to solve that problem, unless those libraries enable a way to parameterize the time. I guess the only option you have there is to run the system in a virtual machine and modify the time outside of Erlang. – Adam Lindberg Oct 04 '15 at 09:29
  • Thank you for the info - I was assuming this might be the case... I will keep question open for a while to see if someone comes up with alternative solution... – jesenko Oct 05 '15 at 07:49
  • 1
    @jesenko, Well, in any case you could just fake time for the whole beam like this http://archive09.linux.com/feature/147801 – Lol4t0 Oct 05 '15 at 12:22
  • For integration tests you could create a gen server with the state that you want in the tests, this article shows how to do it for an API. http://blog.plataformatec.com.br/2015/10/mocks-and-explicit-contracts/ – Migore Sep 06 '16 at 12:10
  • Has this been fixed/added. I'm wanting to test that something doesn't save twice in the same minute, but I cannot check that it will save if the minute changes. – baash05 May 10 '22 at 02:10