marick
TL;DR: I introduce assert_fields and assert_copy and provide their code. Original blog post here.
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.
Trending in Discussions
Other Trending Topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #library
- #deployment
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #elixirconf
- #channels
- #exunit
- #discussion
- #code-sync
- #podcasts
- #javascript
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixirconf-us
- #ai
- #blog-post
- #elixir-ls
- #phoenix_html
- #iex
- #graphql
- #genstage
- #websockets
- #supervisor
- #advent-of-code
- #distillery
- #processes
- #api
- #forms
- #hex
- #security
- #metaprogramming












Most Liked- Show Best Posts
- Show All (oldest first)
- Show All (newest first)
devonestes
Interesting! I actually have a library that I think would suit this case for you (and hopefully provide really great error messages for you as well). Check it out here: assertions | Hex
Is there something in your functions that you can’t easily do with that library? If so, I’d love to add it there. I envision that library as (eventually) a big collection of common assertion abstractions like these ones you mention.
Schultzer
I hope you are aware that you can leverage pattern matching here, so you could simply do all of this in one line.
hauleth
I find it easier to read something like that:
This also makes “predicates” simpler:
Last Post!
marick
Meta:
I was an early adopter of Ruby. In one of the early RubyConfs, maybe the first one, Matz said:
An oddity is that what’s better can depend on circumstances, which is why providing more than one way is better.
I think pattern matching is the bee’s knees. But it can lead to code that isn’t easily to grasp at a glance, at least for some people.
For example, I had tests that had to dissect changesets to look at subcomponents. I find assertions like this more easy to grasp:
I think it’s important to note that:
assert_fieldsstyle.I think it’s OK to offer people options and let them decide, even if they decide differently than I would.