ExUnit: preferred way to test a list element?

I’m TDD’ing parsing code, and I’d been writing tests like this:

    test "First - 001", %{sections_001: sections} do
      assert List.first(sections).number == "1.001"
    end

But then I realized I could also write them list this:

    test "First - 001", %{sections_001: sections} do
      assert [%{number: "1.001"} | _] = sections
    end

I find the first section more readable. But when the list is empty (as it is when TDD’ing this) it gives a long, indirect nil error.

But the second style, which I find less readable, gives a much more readable error when the list is empty:

  1) test Section.number First - 001 (ChapterFileComplexTest)
     test/chapter_file_complex_test.exs:103
     match (=) failed
     code:  assert [%{number: "1.001"} | _] = sections
     left:  [%{number: "1.001"} | _]
     right: []
     stacktrace:
       test/chapter_file_complex_test.exs:104: (test)

Is there another way to do this that I’m not considering?

I often switch things up to whatever I find the most readable for a given test. For your example I think the 2nd approach is great, but I have a couple ideas you could try if you prefer.

If a match gets too visually complex (or wraps in a way I don’t love) I’ll break it up into multiple matches.

assert [section | _] = sections
assert %{number: "1.001"} = section

Or you could always do a hybrid of your two approaches.

# would diff against nil if sections list blank
# otherwise would diff against map
assert %{number: "1.001"} = List.first(sections)
3 Likes

Readability is important, but your two examples are semantically different in how they handle an empty list. First you have to ensure the semantics of what you intend, then choose the more readable of options.

1 Like

One other option to consider is hd/1. It raises on an empty list as the pattern match does, but is a function call like List.first/1

1 Like