Take the 2-minute tour ×

I have a class that I'm testing. The class has a function: apply(List<IRule> rules, List<ITarget> targets);

In one test I want to ensure that each target has been passed to one rule, a la:

rule1.AssertWasCalled(fnord => fnord.Test(target1));
rule1.AssertWasCalled(fnord => fnord.Test(target2));
rule1.AssertWasCalled(fnord => fnord.Test(target3));

It seems to me that limiting myself to a single assertion statement would be quite the hobgoblin. Am I correct in this assumption, or is there some other way that I could assert that each target had, in fact, been tested?

share|improve this question
I can see the fnords! –  Ross Patterson Dec 21 '12 at 0:30
add comment

5 Answers

up vote 15 down vote accepted

The three asserts are in essence one test. You are testing behaviour of a method on a collection, to make sure that each item has been a parameter for a specific call (i.e. that each item has been processed properly).

Setting the data up three times and in three different methods is wasteful and less readable than the alternative of having several asserts.

The single assert "rule" is more about making asserts of different types in the same methods (essentially testing different things), which doesn't really apply in this case, where you are testing for a single behaviour.

share|improve this answer
Indeed : the rule is more one logical assert per Unit Test. You could group those into one higher level assert that you could reuse in different tests. –  Laurent Bourgault-Roy Dec 20 '12 at 21:45
add comment

It's my belief that this one assert per test rule exists to keep your tests focused on one issue. If you test 20 things in one test, it's really hard to tell what your coverage is. You know it's causing a problem when you can't name the test method without the word and in it. For example, if your test method would be more accurately named as testFooIsTrueAndDbExistsAndBarIsNullAndAnExceptionDoesntOccur(), you're probably testing too much in one test.

In your case, I think it's probably ok to assert three times. If you want to make your code more readable, you could extract those three asserts into a method named assertWasCalledOnTargets(...).

share|improve this answer
add comment

For your particular example, you can get away with "one" assert statement if you do something like:

foreach target in targets
     rule1.AssertWasCalled(fnord => fnord.Test(target))

It's what I do to avoid feeling guilty about having multiple asserts in one test.

share|improve this answer
I've done this before. Not a bad way to go. It's easy to read, and you can understand the nature of what its doing. –  CokoBWare Dec 20 '12 at 21:46
add comment

I have struggled with this one, too.

The purist (in me) insists on one assert per test so I'll know *exactly* where things blew up.

And then I find myself cutting / pasting a lot of the same, redundant test setup code. After the third or fourth layer of this, you start to say "Oy! Enough!"

My compromise has been to find the aspects that "never" break. And I'll layer those pieces together and then add in one new element that could break. Just to be clear, layering multiple volatile areas in one test would be a violation of this compromise.

share|improve this answer
You should check out Assume. I just learned about it today. –  Wayne Werner Dec 20 '12 at 21:10
add comment

If the setup code for target1 is different from the setup code for target2, this type of corner-cutting tends to eventually lead overly long test initialization code. This in turn is either a mess or ends up being refactored and re-used. If your tests are complex enough to justify refactoring them, your test is probably testing more than one thing.

If the setup code for each target is essentially the same, splitting your test into multiple individual tests is probably overkill.

If target1 and target2 are different implementations of the same interface, you should instead be adding a unit test to the interface (and allowing your test framework to generate a test for every implementation of that interface).

share|improve this answer
add comment

Your Answer


