Tests that rely on private methods

Most of the dissonance in these discussions come with the disagreement of what a private function means. To me, a private function is an implementation detail. I don’t care how it is named, I don’t care about the argument it receives. If I refactor my private functions and a test breaks, I have a bad test. This is also how the compiler is designed. A private function may not exist at all after the code is compiled.

That’s also why Elixir makes a distinction between code comments and documentation. Code comments are for those reading the source code.

In any case, if a private function has complexity to the point you feel you need to test it and/or document it, then it is most likely worth its own module. And you can still do so while keeping it private and using doctests. To provide an actual example, let’s see how the code above could be rewritten.

Let’s assume @polygonpusher’s code looks like this:

  defmodule User.Invitation do
    def find_invite!(invite_token) do
      Repo.get_by!(User, invite_token: encrypt_token(invite_token))
    end

    defp encrypt_token(token) do
      :crypto.hmac(:sha256, BeffectWeb.Endpoint.config(:secret_key_base), token)
      |> Base.encode16(case: :lower)
    end
  end

I would rewrite it to:

defmodule User.Invitation do
  defmodule Token do
    @moduledoc false

    @doc """
    Now I can doctest this too!
    """
    def encrypt(token) do
      :crypto.hmac(:sha256, BeffectWeb.Endpoint.config(:secret_key_base), token)
      |> Base.encode16(case: :lower)
    end
  end

  def find_invite!(invite_token) do
    Repo.get_by!(User, invite_token: Token.encrypt(invite_token))
  end
end

This way you keep everything in the same file, you provide a logical place for grouping all of the token functionality, you can write tests and doctests and you still don’t expose it to your “final” users.

PS: Note ex_doc now allows custom groups, so you can even have modules targeting different audiences and you can use the grouping functionality to break those apart in the UI.

15 Likes