Scannable map or struct assertions
TL;DR: I introduce
assert_copy and provide their code. (Updated 2019-12-22 with a new syntax for
I used to say "All the words in a test should be about the purpose of the test." I'll probably be exploring some of the ramifications of that slogan throughout the blog. For now, I want to focus on a variant:
All the words I look at in a test should be about my purpose for looking at it.
The "I look at" is because of my new emphasis on scannability. Recall from the previous post that I believe tests should help the reader whose eyes are darting from place to place within a test, searching for an answer to a specific question.
Here are some assertions that improve the scannability of tests involving structs or maps.
Consider code like this:
animal = AnimalT.update_for_success(original.id, params) assert animal.name == "New Bossie" assert animal.lock_version == 2
A while ago, Steve Freeman and I were pairing, and he reacted badly to code like that. In response, I created an
assert_fields function that allows the following:
AnimalT.update_for_success(original.id, params) |> assert_fields(name: "New Bossie", lock_version: 2)
In addition to chaining the assertion (as in the previous post), I like the way syntax highlighting makes the necessary-but-not-enlightening use of
assert_fields fade into the background.
The function tested above produces an updated version of a
struct with three kinds of keys:
- keys that should have been left alone,
- ... keys whose changed value needs to be checked ...
- ... and keys whose new value (if any) should be ignored.
A new function,
assert_copy, works with
assert_fields to handle all three cases in a terse way:
AnimalT.update_for_success(original.id, params) |> assert_copy(original, except: [name: "New Bossie", lock_version: 2], ignoring: [:updated_at])
In the above,
:updated_at is the single field whose new value I don't care about. Perhaps that's not right. Perhaps I want to make sure that
:updated_at has been increased from its original value. I can do that with...
Actually, I won't write an assertion for
:updated_at. It only has a one-second granularity, and I don't want to sleep during tests. Anyway,
:updated_at is set by the Ecto machinery, so I'll believe it's correct if other fields have been changed.
So I'll make up an example. It's a test for a
bossify function where I require the
:tags field to be empty (but I don't care what kind of
Enum it is):
test "sample" do bossify("Bossy") |> assert_fields(name: "Bossy", tags: &Enum.empty?/1) end
Fortunately, Elixir functions generally
inspect nicely, so an assertion's failure message can be nice too:
You can also use predicates in the
:except arguments to
The version as of this writing is here. There are some features not documented in this post.