I have some code like this:
defmodule MyApp.Message.Dispatcher do
alias MyApp.TaskSupervisor
alias MyApp.Message.Foo
alias MyApp.FooTask
@spec dispatch(term()) :: {:ok, term()} | {:error, term()}
def dispatch(%Foo{} = message) do
Task.Supervisor.start_child(TaskSupervisor, FooTask, :run, [[message]])
end
def dispatch(message), do: {:error, {:unknown_message, message}}
end
It receives a struct and depending on the struct it spawns a Task. That Task is calling an external API.
My question now is, how would you test this? Currently I have just added the basics:
defmodule MyApp.Message.DispatcherTest do
use ExUnit.Case, async: true
alias MyApp.Message.Dispatcher
alias MyApp.Message.Foo
alias MyApp.FooTask
describe "dispatch/1" do
test "dispatches a Foo message" do
foo = %Foo{foo: "bar"}
assert {:ok, pid} = Dispatcher.dispatch(foo)
assert is_pid(pid)
end
test "returns an error for an unknown message" do
assert {:error, {:unknown_message, :unknown}} = Dispatcher.dispatch(:unknown)
end
end
end
This doesn’t really test much… What else would you test here and how? I could use Mox or something to mock the Task.Supervisor and check that it is called with the correct arguments, but mocking like that feels really annoying to me. You end up injecting mocks all over the place… I’m trying to keep Mox for things like external API’s that I really don’t want to call.
Another option would be to try and add some expectations on the API mock (3rd party API call) that the FooTask is calling and assert on that… but then this test is really coupled to the implementation of the FooTask.
The third option I can think of is to just leave it like this and have some integration/end-to-end tests that exercise this code path and in that way verify that it’s working.
Any suggestions?