Unit Tests For Generic Methods

You can generate unit tests for generic methods exactly as you do for other methods, as described in How to: Create and Run a Unit Test. The following sections provide information about and examples of creating unit tests for generic methods.

Type Arguments and Type Constraints

When Visual Studio generates a unit test for a generic class, such as MyList<T>, it generates two methods: a generic helper and a test method. If MyList<T> has one or more type constraints, the type argument must satisfy all the type constraints. To make sure that the generic code under test works as expected for all permissible inputs, the test method calls the generic helper method with all the constraints that you want to test.

Examples

The following examples illustrate unit tests for generics:

  • Editing Generated Test Code. This example has two sections, Generated Test Code and Edited Test Code. It shows how to edit the raw test code that is generated from a generic method into a useful test method.

  • Using a Type Constraint. This example shows a unit test for a generic method that uses a type constraint. In this example, the type constraint is not satisfied.

Example 1: Editing Generated Test Code

The test code in this section tests a code-under-test method named SizeOfLinkedList(). This method returns an integer that specifies the number of nodes in the linked list.

The first code sample, in the section Generated Test Code, shows the unedited test code as it was generated by Visual Studio Premium or Visual Studio Ultimate. The second sample, in the section Edited Test Code, shows how you could make it test the functioning of the SizeOfLinkedList method for two different data types, int and char.

This code illustrates two methods:

  • a test helper method, SizeOfLinkedListTestHelper<T>(). By default, a test helper method has "TestHelper" in its name.

  • a test method, SizeOfLinkedListTest(). Every test method is marked with the TestMethod attribute.

Generated Test Code

The following test code was generated from the SizeOfLinkedList() method. Because this is the unedited generated test, it must be modified to correctly test the SizeOfLinkedList method.

public void SizeOfLinkedListTestHelper<T>()
{
    T val = default(T); // TODO: Initialize to an appropriate value
    MyLinkedList<T> target = new MyLinkedList<T>(val); // TODO: Initialize to an appropriate value
    int expected = 0; // TODO: Initialize to an appropriate value
    int actual;
    actual = target.SizeOfLinkedList();
    Assert.AreEqual(expected, actual);
    Assert.Inconclusive("Verify the correctness of this test method.");
}

[TestMethod()]
public void SizeOfLinkedListTest()
{
   SizeOfLinkedListTestHelper<GenericParameterHelper>();
}

In the preceding code, the generic type parameter is GenericParameterHelper. Whereas you can edit it to supply specific data types, as shown in the following example, you could run the test without editing this statement.

Edited Test Code

In the following code, the test method and the test helper method have been edited to make them successfully test the code-under-test method SizeOfLinkedList().

Test Helper Method

The test helper method performs the following steps, which correspond to lines in the code labeled step 1 through step 5.

  1. Create a generic linked list.

  2. Append four nodes to the linked list. The data type of the contents of these nodes is unknown.

  3. Assign the expected size of the linked list to the variable expected.  

  4. Compute the actual size of the linked list and assign it to the variable actual.

  5. Compare actual with expected in an Assert statement. If the actual is not equal to the expected, the test fails.

Test Method

The test method is compiled into the code that is called when you run the test named SizeOfLinkedListTest. It performs the following steps, which correspond to lines in the code labeled step 6 and step 7.

  1. Specify <int> when you call the test helper method, to verify that the test works for integer variables.

  2. Specify <char> when you call the test helper method, to verify that the test works for char variables.  

public void SizeOfLinkedListTestHelper<T>()
{
    T val = default(T); 
    MyLinkedList<T> target = new MyLinkedList<T>(val); // step 1
    for (int i = 0; i < 4; i++) // step 2
    {
        MyLinkedList<T> newNode = new MyLinkedList<T>(val);
        target.Append(newNode);
    }
    int expected = 5; // step 3
    int actual;
    actual = target.SizeOfLinkedList(); // step 4
    Assert.AreEqual(expected, actual); // step 5
}

[TestMethod()]
public void SizeOfLinkedListTest() 
{
    SizeOfLinkedListTestHelper<int>();  // step 6
    SizeOfLinkedListTestHelper<char>(); // step 7
}

Note

Each time the SizeOfLinkedListTest test runs, its TestHelper method is called two times. The assert statement must evaluate to true every time for the test to pass. If the test fails, it might not be clear whether the call that specified <int> or the call that specified <char> caused it to fail. To find the answer, you could examine the call stack, or you could set breakpoints in your test method and then debug while running the test. For more information, see How to: Debug while Running a Test in an ASP.NET Solution.

Example 2: Using a Type Constraint

This example shows a unit test for a generic method that uses a type constraint that is not satisfied. The first section shows code from the code-under-test project. The type constraint is highlighted.

The second section shows code from the test project.

Code-Under-Test Project

using System;
using System.Linq;
using System.Collections.Generic;
using System.Text;

namespace ClassLibrary2
{
    public class Employee
    {
        public Employee(string s, int i)
        {
        }
    }

    public class GenericList<T> where T : Employee
    {
        private class Node
        {
            private T data;
            public T Data
            {
                get { return data; }
                set { data = value; }
            }
        }
    }
}

Test Project

As with all newly generated unit tests, you must add non-inconclusive Assert statements to this unit test to make it return useful results. You do not add them to the method marked with the TestMethod attribute but to the "TestHelper" method, which for this test is named DataTestHelper<T>().

In this example, the generic type parameter T has the constraint where T : Employee. This constraint is not satisfied in the test method. Therefore, the DataTest() method contains an Assert statement that alerts you to the requirement to supply the type constraint that has been placed on T. The message of this Assert statement reads as follows: ("No appropriate type parameter is found to satisfies the type constraint(s) of T. " + "Please call DataTestHelper<T>() with appropriate type parameters.");

In other words, when you call the DataTestHelper<T>() method from the test method, DataTest(), you must pass a parameter of type Employee or a class derived from Employee.

using ClassLibrary2;

using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace TestProject1

{
    [TestClass()]
    public class GenericList_NodeTest
    {
    
        public void DataTestHelper<T>()
            where T : Employee
        {
            GenericList_Shadow<T>.Node target = new GenericList_Shadow<T>.Node(); // TODO: Initialize to an appropriate value
            T expected = default(T); // TODO: Initialize to an appropriate value
            T actual;
            target.Data = expected;
            actual = target.Data;
            Assert.AreEqual(expected, actual);
            Assert.Inconclusive("Verify the correctness of this test method.");
        }

        [TestMethod()]
        public void DataTest()
        {
            Assert.Inconclusive("No appropriate type parameter is found to satisfies the type constraint(s) of T. " +
            "Please call DataTestHelper<T>() with appropriate type parameters.");
        }
    }
}

See Also

Concepts

Anatomy of a Unit Test

Verifying Code by Using Unit Tests