marick

marick

Scannable map or struct assertions

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.

Most Liked

devonestes

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

Schultzer

I hope you are aware that you can leverage pattern matching here, so you could simply do all of this in one line.

assert %Animal{name: "New Bossie", lock_version: 2} = AnimalT.update_for_success(original.id, params)
hauleth

hauleth

I find it easier to read something like that:

assert %{
    name: "New Bossie",
    lock_version: 2
  } = AnimalT.update_for_success(original.id, params)

This also makes “predicates” simpler:

assert %{
    name: "Bossy",
    tags: tags
  } = bossify("Bossy")
assert Enum.empty?(tags)
marick

marick

I use your library, but I didn’t see functions that did the same things. Did I miss something?

I was planning to ask you if you’d accept these functions into your library, so YES to “I’d love to add it there”. How do you feel about chaining assertions (from Chained assertions)? With the exception of things like assert_raise, the return value from an assert_* is meaningless, so it does no harm to provide return values that allow chaining.

I also have a set of Changeset-oriented assertions. I don’t know how they’d fit into a general-purpose library.

marick

marick

Yes. My bias as a person raised in a language culture that reads left-to-right, and so thinks of time as flowing from left to right or top to bottom is that it’s better to put the code that produces the value-to-be-tested before the code that checks that value.

In my Clojure testing library, I illustrated that with book examples

I wouldn’t force that bias on anyone, but for those that share it, this code might be nice.

Where Next?

Popular in Discussions Top

chuck
Let me start by stating an assumption: Phoenix is a great approach to building REST APIs. There are many reasons for this, but I will ass...
New
pillaiindu
I want to convert a Phoenix LiveView CRUD website to a CRUD mobile app. What do you think is the easiest way to do so?
New
ricklove
I was just introduced to Elixir and Phoenix. I was told about the 2 million websocket test that was done 2 years ago. From my research, t...
New
AlexMcConnell
The reason that Rails is as popular as it is is because it’s very easy for relatively inexperienced developers to get a lot of work done....
588 19568 166
New
sergio
There’s a new TIOBE index report that came out that shows Elixir is still not in the top 50 used languages. It also goes on to call Elix...
New
restack_oslo
Hello, Please pardon me for any faux paux. I am 46 and this is my first time on a forum of any kind. I wanted to to get answers from tho...
New
jsonify
So, is Heroku the only free option for hosting Phoenix/Elixir at this point? I’m not ready to commit to paying monthly and was wondering ...
New
tomekowal
Hey guys! I want to create a toy project that shows a chart of temperature over time and updates every 5 seconds. I feel LiveView is per...
New
joeerl
I’m playing with Elixir - It’s fun. I think @rvirding does give Elixir courses these days. Re: files and database - when I given Erlang ...
New
sergio
Kind of like when jquery came out, it was super necessary. Existing drag and drop libraries have a bunch of baggage to support old browse...
New

Other popular topics Top

albydarned
Hello all! I am typing this post from my new MacBook Pro with the M1 chip. I’m loving it so far, and will probably use it as my daily dr...
New
AstonJ
Posting this to see if we can make things easier for people to get into Neovim. If you use Neovim and have a favourite distro please let ...
New
ovidiubadita
Hey all, I discovered Elixir and I love it. I always wanted to learn a functional programming and I intended to go for Haskell, but afte...
New
jononomo
I am trying to figure out how Mix knows whether the environment is test, dev, or prod – where is this set? Thanks.
New
AngeloChecked
What learn first? Rust or Elixir Hi Elixir community! I’m here because i want learn a new language. I’m a junior developer and mainly i ...
New
alice
Hey, Just curious what are the main benefits of Elixir compared to Clojure? When is Elixir more useful than Clojure and vice versa? Th...
New
nobody
Hi! In PHP: $_SERVER[‘SERVER_ADDR’] - in Elixir? Searched the docs for ip address and the web, no good results. Thanks!
New
nsuchy
Hi. I’ve noticed that Windows Powershell has it’s own IEX command and you cannot access Elixir’s IEX due to the conflict. This isn’t a cr...
New
PeterCarter
There are pre-rolled solutions for other frameworks that do work. However, Phoenix does not seem to have these. Have people had good expe...
New
AstonJ
Seen any cool LiveView demos, sample apps or examples? Please post them here! :003:
New

We're in Beta

About us Mission Statement