Scannable map or struct assertions
TL;DR: I introduce assert_fields
and assert_copy
and provide their code. (Updated 2019-12-22 with a new syntax for assert_copy
.)
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.
assert_fields
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.
assert_copy
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...
predicates
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 assert_copy
.
Source
The version as of this writing is here. There are some features not documented in this post.