walkman icon indicating copy to clipboard operation
walkman copied to clipboard

Allow Walkman to play nicely with Mox

Open jonleighton opened this issue 4 years ago • 0 comments

I have an external API under test, let's call it Twitter for the sake of the example.

Suppose my real implementation is MyApp.Twitter.LiveClient, and I am using Mox to be able to implement mocks around this API. So I also have a behaviour, MyApp.Twitter.Client, and a stub client MyApp.Twitter.StubClient. I define a mock client with Mox:

Mox.defmock(MyApp.Twitter.MockClient, for: MyApp.Twitter.Client)

In test mode, my app code is configured to use MyApp.Twitter.MockClient, which allows me to enable the stub client:

Mox.stub_with(MyApp.Twitter.MockClient, MyApp.Twitter.StubClient)

And I can also enable the live client:

Mox.stub_with(MyApp.Twitter.MockClient, MyApp.Twitter.LiveClient)

What I'd like to be able to do is have recorded responses when using the live client. So I install Walkman and do this:

Walkman.def_stub(MyApp.Twitter.RecordedClient, for: MyApp.Twitter.LiveClient)

Unfortunately, if I now try to instruct Mox to switch in the recorded client:

Mox.stub_with(MyApp.Twitter.MockClient, MyApp.Twitter.RecordedClient)

I'll get an error, because MyApp.Twitter.RecordedClient doesn't implement the MyApp.Twitter.Client behaviour.

I can work around the problem by creating a wrapper module, which does implement the behaviour, and delegates to MyApp.Twitter.RecordedClient:

defmodule MyApp.Twitter.RecordedClientWrapper do
  @behaviour MyApp.Twitter.Client

  Enum.each(MyApp.Twitter.Client.behaviour_info(:callbacks), fn {fun, arity} ->
    args = Macro.generate_arguments(arity, MyApp.Twitter.Client)

    @impl MyApp.Twitter.Client
    defdelegate unquote(fun)(unquote_splicing(args)),
      to: MyApp.Twitter.RecordedClient
  end)
end

And now pass that to Mox:

Mox.stub_with(MyApp.Twitter.MockClient, MyApp.Twitter.RecordedClientWrapper)

It would be nice to have support for this use case baked into Walkman.def_stub. Perhaps something like this:

Walkman.def_stub(MyApp.Twitter.RecordedClient, for: MyApp.Twitter.LiveClient, impl: MyApp.Twitter.Client)

Or perhaps it's even possible for Walkman to notice that MyApp.Twitter.LiveClient implements MyApp.Twitter.Client and act accordingly, I don't know...

jonleighton avatar Jan 11 '21 09:01 jonleighton