TestIex — Easier Test Driven Development in Elixir

One of my favorite features of elixir is being able to start a shell that loads the entire context of my project:

$ iex -S mix

It provides easy access to all of the project's modules so you could easily iterate on your code by compiling it directly from within the shell. You have the option to recompile the whole project or just a single module:

# single module
$ r MyModulesNameSpace.MyModule
# whole project
$ recompile

The other great thing about elixir is how easy it is to run unit tests:

# all tests
$ mix test
# single file
$ mix test ./path/test/file/test_file_test.exs
# single test
$ mix test ./path/test/file/test_file_test.exs:<line_number>

When projects are small, mix makes test driven development very quick and easy. Mix also has various test options to optimize testing covering a lot of different use-cases. However, as the project grows, you are likely to have a lot of startup operations that may take dozens of seconds, or even minutes, to complete. It’s fair to say that when an elixir application takes several minutes to start, one should identify the bottlenecks or split a single service into several microservices, but time is not a luxury we always have.

One of the things I wanted to achieve was being able to continuously iterate on my code and tests without having to wait for my whole application to start up every time. Introducing TestIex. This module provides an easy way to iterate on your test and source code, without ever having to exit an iex shell.

1. Start an iex session in the test environment

$ MIX_ENV=test iex -S mix

2. Execute the start testing command

iex> TestIex.start_testing()

3. Load Helpers

By default, TestIex loads the helper located under /usr/src/app/test/test_helper.exs. Additional helpers can be loaded like so:

iex> TestIex.load_helper(“test/test_helper.exs”)

4. Run the test

# Run a single test
iex> TestIex.test("./path/test/file/test_file_test.exs", line_number)
# Run the whole test file
iex> TestIex.test("./path/test/file/test_file_test.exs")
# Run multiple whole test files
iex> TestIex.test(["./path/test/file/test_file_test.exs", "./path/test/file/test_file_2_test.exs"])

4. Iterate on test code

Test files are script files (.exs), which means that they do not get compiled. Modifying a test file and rerunning TestIex.test/2 will execute the latest version of the code.

5. Iterate on source code

Source code files (.ex) need to be recompiled every they are modified. As I’ve shown above, you have the option of recompiling either a single Module or the whole project depending on how many files you modified:

# single module
$ r MyModulesNameSpace.MyModule
# whole project
$ recompile

TestIex — Source Code

Here it the source code for test_iex.ex. I hope it helps you and your team in your TDD endeavors:

defmodule TestIex do
def start_testing() do
ExUnit.start()Code.eval_file("test/test_helper.exs", File.cwd!())
:ok
end
  def load_helper(file_name) do
Code.eval_file(file_name, File.cwd!())
end
  def test(path, line \\ nil)
def test(path, line) when is_binary(path) do
if line do
ExUnit.configure(exclude: [:test], include: [line: line])
else
ExUnit.configure(exclude: [], include: [])
end
    Code.load_file(path)
    if v6_or_higher?() do
ExUnit.Server.modules_loaded()
else
ExUnit.Server.cases_loaded()
end
    ExUnit.run()
end
def test(paths, _line) when is_list(paths) do
ExUnit.configure(exclude: [], include: [])
Enum.map(paths, &Code.load_file/1)
    if v6_or_higher?() do
ExUnit.Server.modules_loaded()
else
ExUnit.Server.cases_loaded()
end
    ExUnit.run()
end
  def v6_or_higher?(), do:
if System.version() |> String.split(".") |> Enum.at(1) |> String.to_integer >= 6 do
end
end