cronokirby / alchemy

A discord library for Elixir
MIT License
152 stars 34 forks source link

How can I test my cogs? #101

Open frahugo opened 3 years ago

frahugo commented 3 years ago

Hi. I'd like to be able to test my cogs using mix test. I looked at Viviani but did not find any tests for the Cogs.

I basically want to make sure a Cog properly performs its job and if I set a custom parser for a command, the right arguments are sent to the function. I would also like a way to inject mocks. Either by adding that to the message variable available in the Cog, or preferably passing options to the command function (see hello below).

It could look like this:

defmodule MyApp.Commands.Hello do
  use Alchemy.Cogs

  Cogs.set_parser(:hello, &List.wrap/1)

  Cogs.def hello(args, opts \\ []) do
    service = Keyword.get(opts, :service, MyApp.HelloService)

    case service.handle(args) do
      {:ok, response} -> Cogs.say(response)
      {:error, _} -> Cogs.say("An error occurred.")
    end
  end
end

defmodule MyApp.Commands.HelloTest do
  use Alchemy.TestCase, async: false

  import Mox

  setup :set_mox_from_context
  setup :verify_on_exit!

  @service_mock __MODULE__.Hello.ServiceMock

  defmock(@service_mock, for: Hello.Behaviour)

  test "command hello responds with Hi", %{cogs: cogs} do
    expect(@service_mock, :handle, fn args ->
      assert args == "my name is Foo"
      {:ok, "Hi Foo!"}
    end

    cog_options = [service: @service_mock]

    response = handle_message("!hello my name is Foo", cog_options)

    assert response.text == "Hi Foo!"
  end
end

Is there anything I can do for now for testing my Cogs without having the source code of Alchemy changed?

cronokirby commented 3 years ago

I think the problem with extra parameters is that you'd need some way to know which commands do or don't have extra parameters, and how to supply them. You could modify the library to always take an extra argument for options, allowing you to call it by filling that parameter in for testing.

My motto for testing commands would be to try and isolate the necessary logic outside of the command itself, which would act as glue for the most part. So you'd be able to test the parsing function in isolation, the logic for choosing what text to return etc.

This requires a bit more work, but is a bit plainer to understand. For integration, it makes more sense IMO to test how these things interact with the actual API, since most problems will be around things like not calling the right effectful function, or things like that.

frahugo commented 3 years ago

Thanks @cronokirby. Yes, the service will definitely be tested out in isolation. It is in fact in a different child app in the umbrella. Here, I just wanted to make sure that I have some tests that covers the glue between Alchemy and my service, to have some peace of mind when in the future I upgrade elixir, alchemy or other parts of the code base.