Why does this assert throw a format exception when comparing structures?
I've got it. And yes, it's a bug.
The problem is that there are two levels of string.Format
going on here.
The first level of formatting is something like:
string template = string.Format("Expected: {0}; Actual: {1}; Message: {2}",
expected, actual, message);
Then we use string.Format
with the parameters you've supplied:
string finalMessage = string.Format(template, parameters);
(Obviously there's cultures being provided, and some sort of sanitization... but not enough.)
That looks fine - unless the expected and actual values themselves end up with braces in, after being converted to a string - which they do for Size
. For example, your first size ends up being converted to:
{Width=0, Height=0}
So the second level of formatting is something like:
string.Format("Expected: {Width=0, Height=0}; Actual: {Width=1, Height=1 }; " +
"Message = Failed expected {0} actually is {1}", struct1, struct2);
... and that's what's failing. Ouch.
Indeed, we can prove this really easily by fooling the formatting to use our parameters for the expected and actual parts:
var x = "{0}";
var y = "{1}";
Assert.AreEqual<object>(x, y, "What a surprise!", "foo", "bar");
The result is:
Assert.AreEqual failed. Expected:<foo>. Actual:<bar>. What a surprise!
Clearly broken, as we weren't expecting foo
nor was the actual value bar
!
Basically this is like a SQL injection attack, but in the rather less scary context of string.Format
.
As a workaround, you can use string.Format
as StriplingWarrior suggests. That avoids the second level of formatting being performed on the result of formatting with the actual/expected values.
I think you've found a bug.
This works (throws an assert exception):
var a = 1;
var b = 2;
Assert.AreEqual(a, b, "Not equal {0} {1}", a, b);
And this works (outputs the message):
var a = new{c=1};
var b = new{c=2};
Console.WriteLine(string.Format("Not equal {0} {1}", a, b));
But this doesn't work (throws a FormatException
):
var a = new{c=1};
var b = new{c=2};
Assert.AreEqual(a, b, "Not equal {0} {1}", a, b);
I can't think of any reason this would be expected behavior. I'd submit a bug report. In the meantime, here's a workaround:
var a = new{c=1};
var b = new{c=2};
Assert.AreEqual(a, b, string.Format("Not equal {0} {1}", a, b));
I agree with @StriplingWarrior that this does indeed appear to be a bug with the Assert.AreEqual() method on at least 2 overloads. As StiplingWarrior has already pointed out, the following fails;
var a = new { c = 1 };
var b = new { c = 2 };
Assert.AreEqual(a, b, "Not equal {0} {1}", a, b);
I've been doing a little experimenting on this further on being a little more explicit in code usage. The following doesn't work either;
// specify variable data type rather than "var"...no effect, still fails
Size a = new Size(0, 0);
Size b = new Size(1, 1);
Assert.AreEqual(a, b, "Not equal {0} {1}", a, b);
And
// specify variable data type and name the type on the generic overload of AreEqual()...no effect, still fails
Size a = new Size(0, 0);
Size b = new Size(1, 1);
Assert.AreEqual<Size>(a, b, "Not equal {0} {1}", a, b);
This got me thinking. System.Drawing.Size is a struct. What about objects? The param list does specify that the list after the string
message is params object[]
. Technically, yes structs are objects...but special kinds of objects, ie, value types. I think this is where the bug lies. If we use our own object with a similar usage and structure to Size
, the following actually does work;
private class MyClass
{
public MyClass(int width, int height)
: base()
{ Width = width; Height = height; }
public int Width { get; set; }
public int Height { get; set; }
}
[TestMethod]
public void TestMethod1()
{
var test1 = new MyClass(0, 0);
var test2 = new MyClass(1, 1);
Assert.AreEqual(test1, test2, "Show me A [{0}] and B [{1}]", test1, test2);
}