DEV Community

David
David

Posted on • Edited on • Originally published at wulymammoth.github.io

Using pipes in your (test) assertions

Love the pipe operator? Miss them in tests with ExUnit when the result is derived from a pipeline of operations necessitating an intermediate variable, like result? I did...

typical test assertion (no pipe operator)

defmodule AdderTest do
  test "add" do
    result =
      1..3
      |> Enum.map(& &1 * 2) # [2, 4, 6]
      |> Adder.add(1) # [3, 5, 7]

    assert result == [3, 5, 7]
  end
end

verbose (with pipe)

We can employ an anonymous function (lambda). NOTE: invoked with dot-syntax

defmodule AdderTest do
  test "add" do
    1..3
    |> Enum.map(& &1 * 2)
    |> Adder.add(1)
    |> (fn result -> assert result == [3, 5, 7] end).()
  end
end

terse (with pipe)

This uses Elixir's & (capture operator) to keep things concise

defmodule AdderTest do
  test "add" do
    1..3
    |> Enum.map(& &1 * 2)
    |> Adder.add(1)
    |> (& assert &1 == [3, 5, 7]).()
  end
end

an option for those that use Elixir's formatter

If you or your team use Elixir's formatter, then you're going to end up with something like the following where the nice whitespace that gives your arguments some room to breathe is removed. One thing that I've done is add two enhancement functions (using macros) into test/test_helper.exs that become available to tests by invoking use TestHelper

defmodule AdderTest do
  test "add" do
    1..3
    |> Enum.map(& &1 * 2)
    |> Adder.add(1)
    |> (&assert(&1 == [3, 5, 7])).() # 🤮
  end
end

Here's what we can do

# test/test_helper.exs

ExUnit.start()

defmodule TestHelper do
  defmacro __using__(_opts) do
    quote do
      import ExUnit.Assertions, only: [assert: 1]

      # we can name this whatever we'd like,
      # but "is" makes sense to me in most cases
      # 👇
      def is(result, expectation) do
        assert result == expectation
        result # 👈 allows us to continue chaining assertions in a pipeline
      end

      # this one allows us to make more complex assertions
      # e.g., asserting that a nested key is of a particular value
      def has(result, assertion) when is_function(assertion) do
        assert assertion.(result) == true
        result
      end
    end
  end
end

# test/adder_test.exs

defmodule AdderTest do
  use TestHelper # 👈 included here

  test "add" do
    1..3
    |> Enum.map(& &1 * 2)
    |> Adder.add(1)
    |> is([3, 5, 7]) # 👈 used here
    |> has(&List.last(&1) == 7) # 👈 and here (chained)
  end
end

Top comments (8)

Collapse
 
kevinkoltz profile image
Kevin Koltz

Seems like you could take it a step further and return result in the is function to pipe multiple assertions

Collapse
 
wulymammoth profile image
David • Edited

You also made me think up some more clever things to try, like returning a partially applied function, such that we can pipe not only values into the assertion but do more complex operations/assertions with a function (e.g. make assertions on nested fields)

Collapse
 
wulymammoth profile image
David

Ah! Why didn't I think of this!? Yes! Clever clever! Thanks, Kevin!

Collapse
 
dgigafox profile image
dgigafox

This is cool. Although, I am pretty sure if you have a group of developers working on a project they will still write the typical one. 😁 And also there are some other things to assert like pattern matching. But still, this looks neat. Thanks for sharing

Collapse
 
wulymammoth profile image
David

Yup! I was just looking for a way to make my tests a bit more concise in a personal project and decided to dig a little before I came up with this, but I do believe that should this be included, whether or not it is standardized in a test suite wouldn't make people balk :)

Collapse
 
pjo336 profile image
Peter Johnston

Oo I like that a lot, nice

Collapse
 
ulissesalmeida profile image
Ulisses Almeida

Great article David!
I'll start to adopt it on my projects.

I miss work with you

Collapse
 
wulymammoth profile image
David

Hey man!!! Me, too! Would be nice to hang out once the crisis is over. Have you updated your book? I recently told someone about it that is starting Elixir. I wrote this recently when I started playing around with Elixir again (almost two years) :D