Testing that a module implements a given behaviour

I have a function which takes a module as an argument. That module must implement certain callbacks.

My use-case is completely analogous to starting a genserver with a callback module.

I was thinking it would be nice to check that the module given actually implemented the correct behaviour. I arrived at the following solution.

 @spec start_link({module, term}, options) :: {:ok, endpoint}
  def start_link(app = {mod, _}, options) do
    mod.module_info[:attributes]
    |> Keyword.get(:behaviour, [])
    |> Enum.member?(Ace.Application)
    |> case do
      true ->
        :ok
      false ->
        Logger.warn("#{__MODULE__}: #{mod} does not implement Ace.Application behaviour.")
    end
    name = Keyword.get(options, :name)
    GenServer.start_link(__MODULE__, {app, options}, [name: name])
  end

I like this solution, but I was looking for opinions.

  • Is it too defensive, should we just assume arguments are correct if they are a module, and deal with function call errors when they occur later.
  • If it’s a good idea it would be nice for things like GenServer et al, to implement such a check.
1 Like

I probably wouldn’t do that, as it’s a developer issue to care about adhering to this requirement and it’s probably not related to any user input. E.g. this could be covered by (integration-) tests, which lets the real code be much more readable and simple while caring about the “does is actually work” in the place that’s meant to check that.

3 Likes

Fair enough. I was learning towards that logic. However because I am in the process of writing a library there is a bit of me that thinks any pointers for mistakes beginners might make would be useful

1 Like

Check out how Ecto does it’s integration tests. There’s a set of shared/generic test cases that different adapters use:

1 Like

I have decided to implement this check, here. In summary I think has the opportunity to be very helpful to beginners. I will keep an eye on feedback, if any, that I get.

1 Like

I know this is an ancient topic but due to interest in the original problem I think it’d be worth it if you share how did you solve it – especially with your link now being dead.

I asked the same question on StackOverflow last year (didn’t see this thread), and thought the answer was pretty good:

It’s essentially the same as what LostKobrakai gave earlier in this thread: when given a module, assume it implements the behaviour. It’s not your behaviour’s job to check.

1 Like

I get the philosophy and do my best to follow it in my work. Often times people have to work with legacy code however – or can’t allocate the time to rearchitect the app – and then having a runtime check is really helpful.

1 Like