Flow style tests
An argument for using assertions that can be arranged in a pipeline, so that a value-under-test flows through a series of them. A single test is improved. Two packages are pointed to.
Last updated
Was this helpful?
An argument for using assertions that can be arranged in a pipeline, so that a value-under-test flows through a series of them. A single test is improved. Two packages are pointed to.
Last updated
Was this helpful?
I like test code like this:
I have two Hex packages that support it: and .
If you know Ecto, you don't need to read this section.
A Changeset
describes proposed changes to some underlying data, typically a named structure. In a typical use, some code takes an original version of the structure and some HTTP form data describing a new version of the structure. The code populates the changes
field (a map) with the values that differ from the original. It will also mark the valid?
field true
if all the changes pass validation tests. If any fail, the errors
keyword list is populated with error messages.
In this example, the changeset is expected to be valid.
This is a pretty typical Elixir test:
VM.ServiceGap.accept_form
(line 1) is the code under test. It accepts some parameters (not shown) and, given those parameters, should a standard Erlang/Elixir :ok
tuple containing a changset.
On line 2, we check that the changeset is valid.
Then (line 4) we bind a variable changes
to the changes
field in the changeset. The remainder of the test asserts that all the changes are as expected.
assert_fields
My first improvement uses assert_fields
to collapse four assertions into one (lines 5 and following):
I think this is more important than it seems because of a fact about how people read tests: they skim. Typically, you'll approach a test with a specific question you want to answer as quickly as possible, like "The change I made to the code broke this test but not others. What's special about this one?"
The code shown above makes the actual fields being tested stand out more. First, they're colorized so they jump to the eye more quickly than they did in the previous version. Second, the lack of assert changes.
means they're more obviously vertically aligned. Humans can skim obvious vertical lists more quickly than they can pick related words from the middle of vertical text, or - for that matter - can skim horizontal lists.
I also converted changeset.changes
into the head of an Elixir |>
pipeline. That makes it stand out more. We're used to reading a pipeline as being "about" the head of the pipeline, so what's being tested is now more clear.
... Except this test isn't really about the changes within a changeset. It's about the changeset itself. Here's a fix:
Line 3 now signals that. Line 4 says that the changeset must be valid, and line 5 says what must be true of the valid changes.
Notice that assert_fields
has turned into assert_changes
. The latter is just a thin wrapper over the former.
:ok
tuple to the pipelineThe :ok
tuple line is pretty verbose for the information it conveys:
It checks for a "shape" that's pretty strongly implied by the rest of the test, it binds a variable that it will turn out we don't really need, and it shoves the information of what's being tested (accept_form
) off to the right where it doesn't catch the eye. Let's fix that:
Now the function under test has pride of place at the top of the pipeline (line 1). ok_content
is a combined assertion (that we have one of those typical :ok
tuples) and it returns the content of the tuple so that the rest of the test can work with it.
Then the rest of the test concerns itself with what must be true of that content.
accept_form
is a function that's common to my emerging style of handling form input. I sometimes have trouble remembering whether it returns a changeset or the Ecto schema underlying the changeset. The test above doesn't help: it's completely lost the word "changeset" when the explicit binding went away. That's usefully fixed on line 2 below:
This variant of ok_content
adds another assertion: that the content within the :ok
tuple is a Changeset
structure.
I find this style of test more readable than the original:
It should be easier to create (fewer words), but I haven't yet persuaded my editor to automatically lay out keyword arguments in pipelines the way I show above, so I (gasp!) have to add indentation myself.
I went to some trouble to make the error output as good as the default ExUnit messages. It was straightforward, in contrast to other libraries I've tried to layer custom assertions on top of. My compliments to the ExUnit authors.
All that's required for this kind of flow-style asserting is that every assertion return its first argument. The package contains a function, , that makes that trivial.
The examples used the and packages. The first depends only on the Elixir kernel. The second depends on Ecto and contains assertions about changesets and Ecto schema structs. Both packages use the , so there's no barrier to copying the assertions you like and leaving the rest behind. I welcome contributions.