TDD with Elixir

What to look for, to do TDD,
in a language you're not familiar with.

I'm new to Elixir

I'm a Software Crafter

We all do tests

at some point…

End user, in prod

Automated test, in dev

Not all code is easy to test

But TDD code is the easiest

Tests
Driven
Development

3 steps, iterative

How to
TDD
with Elixir?

My requirements

  • How can I run tests?
  • Can I have a watch mode?
  • How can I mock things?

Bonuses

  • Can I run a subset of tests easily?
  • Are failing tests reports easy to read?

How can I run tests?

# test_helper.exs
ExUnit.start()
# my_super_module_test.exs
defmodule MySuperModuleTest do
  use ExUnit.Case

  test "add two numbers" do
    result = MySuperModule.add 1, 2

    assert result == 3
  end
end

Standard convention

mix test

Can I have a watch mode?

🎁 Because TDD is awesome
# mix.exs (Elixir 1.4)
def deps do
  [{:mix_test_watch, "~> 0.5", only: :dev, runtime: false}]  
end
# mix.exs (Elixir 1.3 and earlier)
def deps do
  [{:mix_test_watch, "~> 0.5", only: :dev}]
end
mix test.watch

How can I mock things?

Don't rush on mocks

Credits Stefano Alletti

Hexagonal Architecture

Elixir Behaviours

Abstract

Domain-API

Interface

Concrete

Infrastructure

Implementation

Inject a custom one to test

# Domain
module PoetryReader do
  @poetry_library Application.get_env(:poetry_library)

  def give_me_some_poetry do
    @poetry_library.get_a_poem()
    |> do_some_other_transformation
  end
end

module PoetryLibrary do
  @callback get_a_poem() :: {:ok, String.t()}
end
# Infrastructure
module PoetryLibrary.Http do
  @behaviour MyApp.PoetryLibrary
  
  def get_a_poem() do
    url = "https://some-domain.com/poems/random"
    headers = [{"Accept", "application/json"}]

    HTTPoison.get!(url, headers)
    |> Map.get(:body)
    |> decode_response_into_string
  end
end
# Application setup
config :poetry_library, PoetryLibrary.Http

Meet Mox

Mocks and explicit contracts in Elixir

def deps do
  [{:mox, "~> 0.3", only: :test}]  
end
# test/test_helper.ex
Mox.defmock(PoetryLibrary.Mock, for: PoetryLibrary)
Application.put_env(:poetry_library, PoetryLibrary.Mock)
# As a stub
PoetryLibrary.Mock
  |> stub(:get_a_poem, fn() -> {:ok, "Some verses for you"} end)

# As a mock
PoetryLibrary.Mock
  |> expect(:get_a_poem, fn() -> {:ok, "Some verses for you"} end)

Bonuses

mix test.watch --stale

Are failing tests reports readable?

ExDocs tests

📚 Ensure your docs examples are up-to-date

defmodule MySuperModule do
  @doc """
  Return a list of things.

  ## Examples

    iex> MySuperModule.list()
    [1, 4, true, 3]
  """
  def list(), do: [1, 4, true, 3]
end
defmodule MySuperModuleTest do
  use ExUnit.Case
  doctest MySuperModule

  # …
end

If you're going to write crappy code
in a new language…
Do TDD, so you can refactor later!

Going further

About me

@nicoespeon 
Web developer at Busbud 🚎

#Crafter #Meetup #FRP #Agile

Made with Slides.com