Avoiding flaky tests in Phoenix

There is far and way a single type of assertion I’ve see that is responsible for more test flakiness than any other. It’s probably the culprit for more than half of all test failures that shouldn’t fail.

Can you guess what it is?

assert list == [user_1, user_2, user_3]

It also has a cousin that is responsible for many other test failures when in fact nothing should have failed.

assert [%User{id: ^user_1_id, posts: posts} | _] = data

Why are these tests often the source of flakiness? Well, if you’re dealing with Phoenix applications, you’re potentially dealing with a database. And if you’re dealing with a database, you might be selecting things from that database. And if you’re selecting things from that database, and you don’t have an explicit ORDER BY clause in your query, then there is no guarantee for the order in which your records are returned!

Sure, most of the time your tests run this might not fail, as the default behavior for many databases is to return the records in the order they’re stored on disk, and if you created user_1 before user_2, you’ll probably get them in that order most of the time. But it’s much better to be explicit in avoiding this possibility of flakiness, which also does a better job of describing the behavior of the function you’re testing. Otherwise you’re accidentally testing for a certain order of data even if that order isn’t necessarily part of the behavior you need.

To solve the first case above, you can write a function called contains_exactly/2 in a module with some helper functions which will assert that the data you expect to be the contents of a list really is the content of that list, but without asserting the order:

def contains_exactly(list_1, list_2) do
  list_1 -- list_2 == [] and list_2 -- list_1 == []

It’s just a wrapper around two boolean checks, but it gives a good name to what you’re doing and reads well in your tests. It’s also not a very high performing function, but in most test data we’re not usually dealing with dozens or hundreds of records - usually just a couple.

So, unless the order of the lists you’re asserting against is explicitly part of the behavior of the function you’re testing, it’s best to avoid direct equality comparisons of lists if possible!