Reseting mocks' expectations mid-test via Mox

Hi all!

I’ve been working with Mox for a while now and I developed a small DSL for an application I’m building which allowed me in some way to set up certain mocks to model “the world”. The thing is, currently I started developing a feature which required me to change that “world” mid-test – I needed to set the mocks, do some operations, and then reset the mocks so they would return different things, and finally do some more operations.

When trying to achieve this, I saw that I wasn’t able to… but I’m kind of lost as in to why. On the one hand, a part of me understand that mocks should be deterministic, so I should potentially find a different way of doing this, but on the other hand, I really liked the metaphor of having an API which would allow me to interact with the “world” with some really expressive functions. And I also want to understand what’s happening under the hood.

The code I’m trying to make work looks like the following:

    World.new()
    |> World.with_disruption("circle", "Severe Delays", "Description about delays")
    |> World.create()

    StatusBroker.fetch_latest()

    World.new()
    |> World.with_disruption("northern", "Everything is broken", "oooops")
    |> World.create()

    diff = StatusBroker.changes()

    # assert stuff here

And it’s that World.create/0 function which sets up several mocks like the following:

    Application.get_env(:londibot, :subscription_store)
    |> expect(:save, @expected_executions, fn _ -> :ok end)
    |> expect(:all, @expected_executions, fn -> subscriptions end)
    |> expect(:fetch, @expected_executions, fn id -> Enum.find(subscriptions, &(&1.id == id)) end)

Here is the world.ex in case anybody wants more context.

If anybody has any insight as in to why I can’t reset those expects in the mocks, it would be awesome!

Thanks in advance!

1 Like

I started digging into Mox’s code and found the reason for the behaviour :slight_smile:

It looks like the Genserver which holds onto the expectations adds new expectations to the end of the list, instead of replacing… so that explains the why.

I figured that by resetting the Mox Server, it would work, and it did:

    StatusBroker.fetch_latest()

    Mox.Server.exit(self())
    Application.ensure_all_started(:mox)

    World.new()
    |> World.with_disruption("northen", "Everything is broken", "oooops")
    |> World.create()

    diff = StatusBroker.changes()

Nonetheless, that’s a hack, and I should find a proper solution to the problem. I’m wondering though, how would you approach solving this? One option is for the World.create/1 function to always re-start the Mox server, just like above. Another could be setting the explicit number of expected calls I want in each test. The issue is that that would make the API a little extra verbose, something like:

    World.new()
    |> World.with_disruption("victoria", "Severe Delays", "Due to passenger not minding the gap")
    |> World.add_expected_calls(:tfl_service, 2)
    |> World.add_expected_calls(:notification_service, 1)
    |> World.create()

Please read the last paragraph in the documentation for expect: https://hexdocs.pm/mox/Mox.html#expect/4

You should be able to define multiple mocks for the same behaviour, allowing you to change the functionality between invocations.

Application.get_env(:londibot, :subscription_store)
    |> expect(:save, 1, fn _ -> :ok end)
    |> expect(:save, 1, fn -> :not_ok end)

Yup! That’s what I figured could be one solution, but it would force me to change the API of my World module to explicitly state the amount of expected calls I want, which would overall make the whole API more verbose :man_shrugging:

I do feel that it might just be the best solution nonetheless. Maybe I could make it looks like:

    World.new()
    |> World.with_disruption("victoria", "Severe Delays", "Due to passenger not minding the gap")
    |> World.add_expected_tfl_api_calls(2)
    |> World.add_expected_notifier_calls(1)
    |> World.create()

I just thought it looked so clean before!

I think verbosity is a reasonable trade off when writing tests.

1 Like