Paradox

Paradox

Blog Post: Some Elixir Testing Tricks

Testing is an important part of any modern piece of software. But writing tests can quickly become an exercise in frustration, with tons of repeated code, duplicate setup routines, and cumbersome assertions. ExUnit, Elixir’s testing framework, is ultimately just pure Elixir, and so we can extend it using more elixir features. Isolated setup units that can be mixed and matched, configuration via tags, and custom assertions are trivial to add to a test suite, and can save tons of developer headache

Most Liked

Paradox

Paradox

I’ll look into improving them in an Elixir PR this weekend!

Indeed. But since tags are handled by the compiler (they’re just attributes, you can register your own for anything via Module.register_attribute/3. This is how some tools like Absinthe register their documentation and such.

Interestingly, ExUnit also provides a register_attribute/2, which can be used to register attributes for use only in tests. They show up under context.registered, i.e.:

@fixture :foo
# registers as
context.registered.fixture == :foo

There’s also a sibling one for describe attributes, register_describe_attribute/2 and one for modules, register_module_attribute/2, which can be used as described.

I’ve used the TAP formatter in the past to check test names. You can also follow the red-green-red pattern of testing, where you make the tests deliberately fail on the first run, to check the setup. If the test is empty you’ll get a warning about it not being implemented, and if you just want to force a fail you can assert false.

linusdm

linusdm

Very well written and useful. Thx for sharing!

It prompted me to review the documentation of the ExUnit.Case module. I’ve never used tags before, but the examples you provide in the blogpost are very clear. I’d even argue that you could easily improve the existing docs on tags with an example from your post. I wouldn’t have guessed that the tags are passed into the setup callbacks, and take advantage of them (combined with the pattern matching superpowers) like you showed. Super handy indeed. It could as well have worked the other way: setup callbacks are run first, and the tag value is merged into the context last. That’s not clear from the current documentation on tags, afaik.

While reading up on the tag docs I also noticed that you can do:

@tag :admin

which is equivalent to:

@tag admin: true

This spares another four characters :slight_smile:

The last part on how to generate tests with the for-comprehension prompted another question: can you run the tests in a way that it prints out all the test names that are run, also if they pass? When you’re generating tests like that, it’s nice to see the generated test name at least once, to see if it will make sens if they fail. I know you should start with a failing test, but still… :slight_smile: Would be nice to have a formatter that prints more than just a green dot for every passing test. There is a CLI option to override the default formatter, but I didn’t find any bundled formatter that I could pass in.

sodapopcan

sodapopcan

I very much echo the “great article” sentiments and thank you for making me aware of TAP!

I have a somewhat similar helper as your assert_html for LiveView tests to narrow down text within a certain element which I find myself doing a fair bit often:

defmacro assert_within(lv, selector, text_or_regex) do
  quote do
    assert unquote(lv)
           |> element(unquote(selector))
           |> render() =~ unquote(text_or_regex)
  end
end

assert_within(lv, ".comments", "Product was definitely broken when it arrived, please send another.")
sodapopcan

sodapopcan

Yep—it crossed my mind to do that and considered mentioning it here but didn’t want to give anyone any ideas :sweat_smile: I think it’s a bit gnarly and of course it stays pipeable if you directly accept the dependency. I’ve abandon every idea I’ve had like this after shooting myself in the foot with Ruby over the years. You technically don’t even need a macro to make it work as it is, but it’s a little bit simpler sorta.

trisolaran

trisolaran

Thanks. I asked because I’ve been writing this kind of helpers for at least a couple of years now and I never thought about writing them as macros, so I was wondering if there was a deeper reason behind your choice.

Where Next?

Popular in Blog Posts Top

Qqwy
Update: How to use the Blogs & Podcasts section You can post links to your blog posts or podcasts either in one of the Official Blog...
3268 119930 1237
New
brainlid
This post asks if we can remove Alpine from the PETAL stack. Can we do everything we need with just LiveView? Also, let’s explore an area...
New
brainlid
Livebook was created for machine learning in Elixir but Livebook isn’t limited to machine learning. I found it works really well for docu...
New
danschultzer
How to set up the Content Security Policy header with Phoenix LiveView and support inline styles and scripts with CSP nonces.
New
bencoppock
I write about the frustration that can occur when dealing with string and atom-based keys in maps and how to (safely) translate string-ba...
New
pillaiindu
Our very handsome and humble José Valim (@josevalim) just published Kubernetes and the Erlang VM: orchestration on the large and the sma...
New
sashaafm
How to use the blogs section You can post links to your blog posts either in one of the Official Blog Posts threads (like this one), or, ...
296 24412 135
New
mudasobwa
Just blogged on the general approach to creating easily extendable applications with plugin support.
New
Paradox
Testing is an important part of any modern piece of software. But writing tests can quickly become an exercise in frustration, with tons ...
New
paulanthonywilson
This is an overview of different ways to try and kill an OTP process (in Elixir) and the behaviour to expect when that happens. I know th...
New

Other popular topics Top

Qqwy
Update: How to use the Blogs & Podcasts section You can post links to your blog posts or podcasts either in one of the Official Blog...
3268 119930 1237
New
Patoshizzle
After calling mix ecto.create I get this error: 17:00:32.162 [error] GenServer #PID<0.412.0> terminating ** (Postgrex.Error) FATAL...
New
josevalim
Hi everyone, One of the features added to Elixir early on to help integration with Erlang code was the idea of overridable function defi...
New
SoCreat
i’m a new one to elixir which editor can i use vs code? or atom? Thanks! :smiley:
New
dotdotdotPaul
Okay, I'm having a heck of a time trying to figure out how to best handle the validation of belongs_to associations in Ecto. I'm sure I'...
New
gshaw
What is the idiomatic way of matching for not nil in Elixir? E.g., First way: defp halt_if_not_signed_in(conn, signed_in_account) when...
New
yawaramin
In the Dialyzer docs ( http://erlang.org/doc/man/dialyzer.html#requesting-or-suppressing-warnings-in-source-files ), there is a way to tu...
New
jerry
Good day to you all. I have been struggling to get a query involving like and ilike to work. Can anyone assist me on this, please? pro...
New
qwerescape
Is there a way to get the call stack or stack trace at any point in the code? Not from exceptions, but an expression that returns how the...
New
jay1
Why is it that the mnesia database isn’t the most preferred database for use in Elixir/Phoenix?
New

We're in Beta

About us Mission Statement