C# – Assert equality in mstest when types may differ

cdata typestesting

I've been working on some MSTest automated test infrastructure, that is testing a tool that merges data sets into SQL Server database tables. The basic structure of the test is to:

  1. Define the incoming dataset using anonymous types
  2. Apply the data using the reconcile tool
  3. Read records from the output tables
  4. Compare result rows to input data, column by column

Example input data:

public class InputData : List<dynamic> {} // Inspired by Massive etc

InputData input = new InputData()
{
    new { ExternalID = 1, PropertyName = "hello", Agent = "test" },
    new { ExternalID = (Int64)2, PropertyName = "fred", Agent = "test" } // Fixes the problem with a cast
};

The issue I'm dealing with, is that the type inferred on my anonymous objects will be an Int32, but the corresponding column in my target table is a bigint, and hence the record will have an Int64 value. As a result, when I use Assert.AreEqual across each column, it fails on any int fields:

Assert.AreEqual failed. Expected:<1 (System.Int32)>. Actual:<1 (System.Int64)>

You can see I have cast the int on my second anonymous object, this can be used to fix the issue. The primary aim of these tests is to make the sample data as slim and easy to read/write as possible, and I'd prefer to avoid the visual noise of all those casts.

I'm thinking about the best way to deal with the assertions. It seems like I should use dedicated assertions based on type. I guess the real question is, how aggressive should I be in converting between types automatically?

Best Answer

Assert.AreEqual is basically just sugar for Assert.IsTrue(object.Equals(...)). If there isn't a known comparison operator for the two argument types, they'll compare false.

dynamic is pretty smart about this, so, for example, (dynamic)(int)1 == (decimal)1.0 evaluates to true. That only works if you use the == operator, though; it won't work if you try to use object.Equals, Assert.AreEqual, ((dynamic)x).Equals(...) or anything similar. Simply casting to dynamic won't work, you need to actually use its == operator. The good news is, the dynamic == allows any operand, which means you can get this to compile in a general-purpose assertion; if the comparison is invalid (e.g. comparing int to string, then it will fail at runtime with a RuntimeBinderException.

The next best thing, if you know they're going to be numeric types, would be to cast them all to Decimal or Double, although note that if you use Double you'll have to take into account the possibility of floating-point rounding error, and you should define equality based on some threshold, typically a multiple of Double.Epsilon. Decimal is a lot simpler unless you know for certain that you'll be dealing with floating point.

I don't think that you really have a lot of other choices. I personally would not recommend string conversion because lots of classes don't override ToString, so you end up comparing two instances of something like System.Collections.Generic.List``1[System.Int32], and your assertion declares that these are equal because they have the same string representation when in fact they are not only different instances but contain completely different elements. So don't do that. String coercion can easily mask serious errors in your tests. Stick to non-lossy, all-or-nothing conversions as above.