DEV Community

sampriddy
sampriddy

Posted on

Works as shown: Elixir's doctest macro

What is doctest?

doctest is a macro that searches a specified module for code examples and automatically generates test cases.

Why use doctest?

Primarily to ensure the correctness of your module documentation. Doctest examples are also easy to write, can replace simple unit tests, and provide a standard format for specifying code examples and demonstrating your module's API.

Untested documentation trends toward incorrectness over time and eventually becomes worse than not having documentation at all.

A small example

# lib/small_math.ex
defmodule SmallMath do
  @doc """
  Adds two numbers together.

  ## Examples
    iex> SmallMath.add(1, 2)
    3

    iex> SmallMath.add(5, 7)
    23
  """
  def add(a, b), do: a + b
end

# test/small_math_test.exs
defmodule SmallMathTest
  use ExUnit.Case, async: true
  doctest SmallMath
end
Enter fullscreen mode Exit fullscreen mode

Output

$ mix test

Compiling 1 file (.ex)
.

  1) doctest SmallMath.add/2 (2) (SmallMathTest)
     test/small_math_test.exs:3
     Doctest failed
     code: SmallMath.add(5, 7) === 23
     left: 12
     stacktrace:
       lib/small_math.ex:9: SmallMath (module)

Finished in 0.03 seconds
2 doctests, 1 failure


Randomized with seed 888764
Enter fullscreen mode Exit fullscreen mode

As we see, doctest generated two test cases from our documentation, one of which failed.

How does it work?

doctest looks for code examples by searching for lines beginning with iex> and evaluates them as if it were a user sitting at the console. Afterward, a line that specifies a bare value (i.e. does not begin with iex> or ...>) is interpreted as a result, and a test is generated to assert that the value specified is what a user would see if they had evaluated the last expression.

@doc """
iex> 5 + 5  # expression
10          # result
"""
Enter fullscreen mode Exit fullscreen mode

Multiline expressions

You can use ...> to build a multiline expression.

@doc """
  iex> add_one_fn = fn(number) ->
  ...>   number + 1
  ...> end
  iex> add_one_fn.(5)
  6
"""
Enter fullscreen mode Exit fullscreen mode

Testing for exceptions

Writing an example demonstrating an exception is also supported!

@doc """
  iex> 1 + "hello"
  ** (ArithmeticError) bad argument in arithmetic expression
"""
Enter fullscreen mode Exit fullscreen mode

Caveats

Tests generated by doctest do not run in isolation of each other. Code that generates side effects is not a good match. Binding variables or defining code inside of a doctest also carries the risk that your code will be reused in another test.

Top comments (1)

Collapse
 
dmerand profile image
Donald Merand

Interesting note about side-effects in doctest code - I did not know that!