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

First Post!

dimitarvp

dimitarvp

Thanks, pretty useful. I already used part of these so I am definitely stealing using the others.

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.")

Last Post!

lud

lud

This is my current command to run the tests:

mix test --failed --max-failures 1 --seed $(odo -c _build/seed.txt) && \
mix test --stale  --max-failures 1 --seed $(odo -c _build/seed.txt) && \
odo _build/seed.txt

odo is a simple counter that reads or increments a number from a file.

So what is does is:

  • as long as I have a failing test, it will only run that test, thanks to the seed
  • when this test passes, it will now fail repeatedly on the next failing test
  • when all my previously failing tests pass, it will run all the stale tests. If one of them fails, the loop will start over.
  • if everything passes, we increment the seed to preserve a minimum of entropy like designed in ExUnit.

A simple shortcut for vscode:

  {
    "key": "ctrl+alt+u",
    "command": "workbench.action.terminal.sendSequence",
    "args": {
      "text": "mix test --failed --max-failures 1 --seed $(odo -c _build/seed.txt) && mix test --stale --max-failures 1 --seed $(odo -c _build/seed.txt) && odo _build/seed.txt\n"
    }
  },

Where Next?

Popular in Blog Posts Top

shanesveller
Introduction and first content post in my new series about running Elixir Phoenix apps on Kubernetes are live! I’m describing an opiniona...
New
AstonJ
Update: How to use the blogs section You can post in one of the Official Blog Posts threads (like this one), or, via Devtalk and a new t...
New
paulanthonywilson
So you’re enjoying using WebSockets with Elixir’s Phoenix Framework, and you want to send some binary messages. Maybe it’s an audio clip,...
New
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
ryotsu
How does a torrent client download huge files from the internet with just a .torrent file? In this post we’ll explore the BitTorrent prot...
New
psantos
Just wrote a new blog post about how to deploy a Phoenix app using Kamal 2. Check it out and let me know what do you think: https://blog...
New

Other popular topics Top

joaquinalcerro
Hi there, I am working with Ecto-Postgresql and I need to call all of the records from a specific table but the table has 40,000 records...
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
chrismccord
Phoenix 1.4.0 released Phoenix 1.4 is out! This release ships with exciting new features, most notably with HTTP2 support, improved deve...
688 31525 112
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
siddhant3030
Hi, I have to write a raw query for one of my project. But till now I have used ecto queries and don’t have much experience writing raw ...
New
JorisKok
I have a server on AWS, and was running a load test using artillery. When looking at the Phoenix dashboard I see the Ports going to 100% ...
New