Negative assertions in tests are problematic.
Positive assertions like
page.should have_content("Welcome") simply pass as long as that content is present, and fail if is is removed.
But negative assertions like
page.should_not have_selector(".widget") or
Notifier.should_not_receive(:invoice_overdue) may pass because your code works as intended – or because you renamed
thingy and forgot to update the test.
Negative assertions are (hopefully) meaningful when you write them, but after that, there are no guarantees they’ll stay that way.
So what can you do about it?
Balance with a DRY opposite
You can make negative assertions a lot more reliable by also making the opposite assertion (when the thing should happen) and making sure to share the common reference:
1 2 3 4 5 6 7 8 9 10 11 12 13
This way, if you rename
widget, your positive assertion will fail. When you make the positive assertion pass again, the negative assertion will automatically stay relevant.
The common reference could be a method call like
have_selector(…) above, or just a shared selector like
".widget", or a string of copy:
1 2 3 4 5 6 7
The commonalities could be shared by way of a method, like we just did (
def have_widget), or it could be an RSpec
let statement, or a constant.
Adjacent helper methods
Sometimes, making the common code DRY makes the test hard to read. Adjacent helper methods may be good enough:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
Because the positive and negative assertions are adjacent, and the notifier class and message are only in those two places, the negative assertion is far less likely to become irrelevant.
They could be made completely DRY, sharing the
Notifier class name and
:invoice_overdue message name, but the above may be enough. And if it’s not, the helper methods will at least make them more readable:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
Aside from making negative assertions more reliable, helper methods like these have the additional benefit of making the tests read better, because they tell the story at a higher level of abstraction.