How to setup data when writing test in Elixir

Pau RiosaPau Riosa
3 min read

Story

Recently, I decided to update several test files that uses the same test setup for their data. Mostly the code was written with the same structure

# assuming you are using ExMachina package
inserted_at = DateTime.utc_now() |> DateTime.add(-30, :day) |> DateTime.to_naive
organization = insert(:organization, inserted_at: inserted_at)

In the above code, I want all my test setup to have this kind of data but in order for me to do that I need to repeat this code in every unit test.

test "test 1" do
    inserted_at = DateTime.utc_now() |> DateTime.add(-30, :day) |> DateTime.to_naive
    organization = insert(:organization, inserted_at: inserted_at)
end

test "test 2" do
    inserted_at = DateTime.utc_now() |> DateTime.add(-30, :day) |> DateTime.to_naive
    organization = insert(:organization, inserted_at: inserted_at)
end

As much as possible we don’t want repeated code just like the above.

First Solution

ExUnit has a way how to conveniently setup your whole test to have the same setup. The easiest way is to make a private function and invoke it before your tests or inside your describe block.

setup [:setup_organization]

test "test 1", %{organization: organization} do
    # do test 1 logic here
end

test "test 2", %{organization: organization} do
    # do test 2 logic here
end

defp setup_organization(context) do
 inserted_at = DateTime.utc_now() |> DateTime.add(-30, :day) |> DateTime.to_naive
 organization = insert(:organization, inserted_at: inserted_at)

 {:ok, Map.put(context, :organization, organization)
end

Now, this one seems already nice. We manage to DRY the logic for setting up the organization data, call setup/1 and put setup_organization to one of the setups.

But here is the thing, I still need to update every test file that uses this same logic. I do not want to copy all of these code since it is going to create repeated codes again.

Second Solution

In order for me to have this setup to all test files that needs this, I decided to create a helper module that implements the same context function we created in one file.

defmodule TestHelper do
    @moduledoc false

    @spec setup_organization(context :: map()) :: {:ok, map()}
    def setup_organization(context) do
         inserted_at = DateTime.utc_now() |> DateTime.add(-30, :day) |> DateTime.to_naive
         organization = insert(:organization, inserted_at: inserted_at)

         {:ok, Map.put(context, :organization, organization)
    end
end
setup [:setup_organization]

test "test 1", %{organization: organization} do
    # do test 1 logic here
end

test "test 2", %{organization: organization} do
    # do test 2 logic here
end

The question is, how can we call the helper function in another module?

Instead of calling the :setup_organization context function like the above we need to tweak how do we call it.

# This is where the magic happens
setup [{TestHelper, :setup_organization}]

test "test 1", %{organization: organization} do
    # do test 1 logic here
end

test "test 2", %{organization: organization} do
    # do test 2 logic here
end

This helper context function can now be accessible to every test files that needs this.

Happy Coding!

References

https://hexdocs.pm/ex_unit/ExUnit.Callbacks.html#setup/1

0
Subscribe to my newsletter

Read articles from Pau Riosa directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Pau Riosa
Pau Riosa

🎥 Youtuber 💻. Software Developer 📕 Blogger I am software developer who has passion for career development and software engineering. I love building stuff that can help people all around the world. Let's connect and share that idea!