25

Can we have a loop inside a unit test?

My method returns an IEnumerable<IEnumerable>, I would like to unit test this logic where the IEnumerable<IEnumerable> is created. Basically I wanna test if the count of elements in the IEnumerable are as expected.

I cannot figure out an alternate way to test the inner IEnumerable without having a looping statement. Please let me know if this is a good practice.

Shankar Raju
  • 4,356
  • 6
  • 33
  • 52

3 Answers3

33

There is no technical reason you can't do it. You can have multiple Assert statements in a unit test. Having an Assert statement in a loop is simply a shorthand way of having multiple Assert statements in a test.

However, some people think there should only be a single Assert statement in a unit test.

I personally don't agree - I think a test should test a single thing - and in order to do that sometimes you may need more than one Assert statement.

If your method returns an IEnumerable of Product's, and each Product contains an IEnumerable of Color's, then I think the following test is is fine:

[Test]
public void All_products_should_have_some_colors()
{
    var products = GetProducts();

    foreach (var product in products)
    {
        Assert.IsNotEmpty(product.Colors);
    }
}

However, you do need to be aware that if the IEnumerable contains 0 elements, the loop will never execute any of the Assert statements and your unit test will "pass" - whereas you probably would have intended it to fail.

To remedy that situation, you could have a separate test making sure that there are more than 0 elements in the IEnumerable (i.e. that GetProducts does actually return some Product's):

Assert.IsNotEmpty(products);
Alex York
  • 5,392
  • 3
  • 31
  • 27
  • Thanks for the reply. The term "acceptable" means whether it is a good practice to do that in an Agile way. – Shankar Raju May 01 '11 at 10:02
  • 2
    I would argue that the test in my answer is "acceptable" for these reasons: 1) it tests a "single thing" (that all products have some colors). 2) it is simple and easy to read. – Alex York May 01 '11 at 10:04
  • Well yeah.. thats all I need. I ask this question because some people say we need only a single Assert in each test, so I wanted to know from them how do we go on testing this method :) – Shankar Raju May 01 '11 at 10:06
  • 2
    I very rarely have more than one Assert statement in a test. But sometimes I do have multiple Asserts, or an Assert in a loop. When I do this, I don't lose any sleep over it. If the test serves its purpose and is well-written - then I move on to the next task :-) – Alex York May 01 '11 at 10:10
  • 2
    good paints. You can make several assert statements, but only one one thing. Best example is to assert that all properties of a return object are correct. But if I use multiple asserts, I usually extract them to a private method, and be careful to give it a meaningful name like AssertThatReturmedAdressObjectHasCorrectProperties. – Morten May 01 '11 at 11:42
8

One reason to avoid writing a loop in a test would be to keep the test concise and readable at a glance. Since you have tagged the question with NUnit and you say you just want to test that the element count is as expected, consider making your Asserts using the NUnit Constraints.

For example,

IEnumerable<IEnumerable<char>> someStrings = new[] { "abc", "cat", "bit", "hat" };

Assert.That(someStrings, Has.All.With.Length.EqualTo(3).And.All.Contains("a"));

fails with the message:

Expected: all items property Length equal to 3 and all items String containing "a" But was: < "abc", "cat", "bit", "hat" >

but passes if you change "bit" to "bat".

The book xUnit Test Patterns: Refactoring Test Code By Gerard Meszaros

has many great answers to questions such as yours.

Grokodile
  • 3,881
  • 5
  • 33
  • 59
  • +1 for suggesting using NUnit constraints, but I would suggest that you'd probably want to split the length and the "contains a" constraints into separate tests, or at least separate asserts. – Rangi Keen Aug 10 '17 at 17:40
2

Yes you can have loops in unit test, but with caution. As mentioned by Alex York, loops are acceptable if you test one thing; i.e. one expectation.

If you use loops, then I recommend that you must do two things:

  1. As mentioned above, test for a non-empty iteration set. Iterating over an empty set is a false positive. False positive results are bane of all automated testing because nobody double checks a green result.
  2. Include a test description that describes the current iteration. At a minimum include the iteration index.

Here is an example from my testing of the Greater Than property of an object.

[Test]
public void TestCompare_XtoY_GreaterThan()
{
  int numObjects = mOrderedList.Count;
  for (int i = 1; i < numObjects; ++i)
  {
    for (int j = 0; j < i; ++j)
    {
      string testDescription = string.Format("{0} is greater than {1} which implies\n  {2}\n    is greater than\n  {3}"
                                            , i, j
                                            , mOrderedList[i], mOrderedList[j]
                                            );
      Assert.IsTrue(0 < mOrderedList[i].CompareTo(mOrderedList[j]), testDescription);
      Assert.IsTrue(0 < mOrderedList[i].Compare(mOrderedList[i], mOrderedList[j]), testDescription);
      Assert.IsTrue(0 < mOrderedList[j].Compare(mOrderedList[i], mOrderedList[j]), testDescription);
      Assert.Greater(mOrderedList[i], mOrderedList[j], testDescription);
    }
  }
}

I test that my ordered list is non-empty in the test setup using:

[SetUp]
public void GeneralTestSetup()
{
  // verify the iterated sources are not empty
  string testDescription = string.Format("The ordered list of objects must have at least 3 elements, but instead has only {0}.", mOrderedList.Count);
  Assert.IsTrue(2 < mOrderedList.Count, testDescription);
}

I have multiple Asserts even within my loop, but all of the asserts are testing the single expectation:

if i > j then mOrderedList[i] > mOrderedList[j]

The test description at the iteration level is so you get Failures such as:

10 is greater than 9 which implies
  TestActionMethodInfo: [Actions.File, Version=1.0.446.0, File, VerifyReadOnly]
    is greater than
  TestActionMethodInfo: [Actions.File, Version=1.0.446.0, File, Write]
Expected: True
But was:  False

instead of just:

Expected: True
But was:  False

The question/debate about my code above:

Is am I testing one thing?

I am asserting on 4 different comparison methods within the object which could be argued as testing 4 things not one. The counter is greater than is greater than is greater than and that all of the methods which make that evaluation should be consistent.

John Washburn
  • 568
  • 1
  • 5
  • 11