Search code examples
unit-testingjunitnunitxunitvs-unit-testing-framework

Unit Testing - Tests Cases vs. Multiple Methods


When writing unit tests for the below example, would it be best practice to use TestCases (such as in NUnit), or to write multiple tests for verify the functionality?

Say that you want to test the RetrieveContact method below - it simply finds the corresponding Contact within an array and returns the result if found, otherwise will return null.

public class Contact
{
    public string Name { get; set; }
}

private static Contact[] Contacts =
{
    new Contact() { Name = "Jim" },
    new Contact() { Name = "Bob" },
    new Contact() { Name = "Tom" }
};

public static Contact RetrieveContact(string name)
{
    return Contacts.FirstOrDefault(c => c.Name == name);
}

Would you test this with one method, using TestCases, like below?

[TestCase("Bob", "Bob")]
[TestCase(null, "ZZZ")]
public void Test_RetrievesFromContacts(string expectedName, string name)
{
    var ret = RetrieveContact(name);
    Assert.AreEqual(expectedName, ret?.Name);
}

Or would you write two separate tests - one for valid contacts, or one for invalid contacts returning null?

[Test]
public void Test_RetrieveValidContact()
{
    var ret = RetrieveContact("Bob");
    Assert.AreEqual("Bob", ret.Name);
}

[Test]
public void Test_RetrieveInvalidContact()
{
    var ret = RetrieveContact("ZZZ");
    Assert.AreEqual(null, ret);
}

Thanks


Solution

  • The convention in JUnit is that each unit test should test one specific piece of functionality, one distinct path through the code-under-test. This convention strongly implies that you should not combine multiple, unrelated tests into a single test method.

    So, in your example above you would write one test for each of the following:

    • one for valid contacts
    • one for invalid contacts

    The rationale for this is:

    • If you combine both of the above test paths into one test then a failure in the first path could prevent the second path from being run (thereby reducing test coverage)
    • All other things being equal, a fine grained test case is likely to be more concise and legible i.e. less likely to be infected with conditionality such as if firstPathFails then log and move on to second path etc
    • A test case which addresses the happy path ("valid contacts" in your case) will not have to be changed if your handling of "invalid contacts" changes. This is a reference to the SRP.

    Notes:

    • If several related methods within a test case require shared setup code then this can be implemented in a @Before method thereby reducing the size/reposibilities of each @Test method.
    • The above convention does not imply that a single test method can only have one assertion.
    • The above convention does not necessarily apply to coarse grained tests, such as integration tests, where an entire slice of an application may be tested.