TDD in Elixir with ExUnit and Doctest

ExUnit & DocTest

Elixir has it's own test framework built in natively, called ExUnit. ExUnit is a core component of Elixir itself, as much as the task runner and dependency manager mix. When you start a new project with mix, everything is directly set up for you, including basic unit tests for your first module and preconfigured tasks to do TDD right away.

In addition to traditional test suites, there is also another (often overlooked) feature, called DocTests. This means that you can copy a sample IEx call of your function, including the result, and paste it directly into a documentation block above the function in your code. When you run your test suite, these snippets are picked up by ExUnit and executed along with your tests! Using these DocTests you can ensure that comments will say the truth, even if someone changes the code of the function (because a test error will pop up that wants to be fixed).

When not to use doctest

In general, doctests are not recommended when your code examples contain side effects. For example, if a doctest prints to standard output, doctest will not try to capture the output.

Similarly, doctests do not run in any kind of sandbox. So any module defined in a code example is going to linger throughout the whole test suite run.

Example:

An example app can be found here:

defmodule SchizoTest do
  use ExUnit.Case
  doctest Schizo

  test "uppercase doesnt change the first word" do
    assert(Schizo.uppercase("foo") === "foo")
  end

  test "uppercase converts the second word to uppercase" do
    assert(Schizo.uppercase("foo bar") == "foo BAR")
  end

  test "uppercase converts every other word to uppercase" do
    assert(Schizo.uppercase("foo bar baz whee") == "foo BAR BAZ WHEE")
  end

  test "unvowel doesnt change the first word" do
    assert(Schizo.unvowel("foo") === "foo")
  end

  test "unvowel  removes the second words vowels" do
    assert(Schizo.unvowel("foo bar") == "foo br")
  end

  test "unvowel removes every other words vowels" do
    assert(Schizo.unvowel("foo bar baz whee") == "foo br bz wh")
  end
end

and the accompanying code for the above test:

defmodule Schizo do
  @moduledoc """
    A nice module that lets you upcase or unvowel from every other word in a sentence
  """

  @doc """
    Uppercase every other word in a sentence, Example:
    iex> Schizo.uppercase("you are awesome")
    "you ARE AWESOME"
  """
  def uppercase(string) do
    transformer(string, &upcaser/1)
  end

  @doc """
    Removes vowels from every other word in a sentence. Example:

    iex> Schizo.unvowel("you are silly")
    "you r slly"
  """
  def unvowel(string) do
    transformer(string, &unvoweler/1)
  end

  defp transformer(string, transformation) do
    string
      |> String.split()
      |> Stream.with_index
      |> Enum.map(transformation)
      |> Enum.join(" ")
  end

  defp upcaser(input) do
    transform(input, &String.upcase/1)
  end

  defp unvoweler(input) do
    transform(input, fn (word) -> Regex.replace(~r/[aeiou]/, word, "")  end)
  end

  defp transform({word, index}, transformation) do
    case {word, index} do
      {word, 0} -> word
      _         -> transformation.(word)
    end
  end
end