This is a post in my Zero-Friction TDD series.
In most cases, I’m very happy with the C# compiler’s ability to infer generic type arguments from the method call parameters, but this case is a little different.
Let’s say you want to compare two strings. I always do it like this:
Assert.AreEqual<string>(expectedResult, result, “DoStuff”);
This statement explicitly tells the compiler to use AreEqual<string>. However, if I omit the type parameter, type inferencing is not going to happen:
Assert.AreEqual(expectedResult, result, “DoStuff”);
Instead of inferring the type parameter from the parameters, the compiler (correctly) selects the best matching overload, in this case, AreEqual(object, object, string).
When both the expectedResult and result variables are strings, this will still compile and work, but you just lost static type checking.
There are 18 overloads of AreEqual, but that still leaves many combinations where type inferencing will occur. Consider comparing two integers:
Assert.AreEqual<int>(expectedNumber, result, “Explicit typing”);
Since there’s no non-generic overload that takes Int32 instances, you could omit the type parameter and still arrive at the AreEqual<int> overload:
Assert.AreEqual(expectedNumber, result, “Implicit typing”);
However, what happens if you change the type of result to a string? In the first case, you will get a compilation error, but in the latter case, the compiler instead selects the AreEqual(object, object, string) overload!
When you perform refactorings that change the type of one of the assertion variables, the tests will still compile if you rely on type inferencing, but may fail at run-time. With the refactoring support in Visual Studio, you may be changing the type of a property somewhere else in your code base without thinking about the test code at all. If you test directly against such a property in your AreEqual assertion, static type checking could protect you, but the non-generic overloads may introduce subtle errors in your tests.
Since test suites should be executed often, you might argue that you’ll discover errors like that soon enough, but I’m a strong believer in fail fast, so I still rather prefer getting a compiler error than a run-time error in my test suite.
The easiest strategy is to not think about this at all, and just always explicitly define the type, so that’s what I do: I always use AreEqual<T> and AreNotEqual<T> (except in special cases, but more about that later).