Back to basics, AreSame vs. AreEqual

I thought I would do a little back to basics today as I was working with some people (new to unit testing) and had to explain the difference between calling Assert.AreEqual vs. Assert.AreSame.

On the surface, they look identical. They both test expected and actual items and the test will fail if the items don’t match. So here's a simple test:

[Test]
public void TestAreSameStrings()
{
  string expected = "MyString";
  string actual = "MyString";
  Assert.AreSame(expected, actual);
}

So this test fails if the strings are different. Obviously they are the same but imagine if we retrieved the string from a property, web service, etc. Then we might be testing the return value from a business entity against a known string value. This test written using Assert.AreEqual instead of Assert.AreSame will be the same result, pass (or green in the NUnit GUI).

However what if we use integers instead of strings like so:

[Test]
public void TestAreSameIntegers()
{
  int expected = 1;
  int actual = 1;
  Assert.AreSame(expected, actual);
}

This test actually fails. It looks like it should pass as it's no different than the string test above right? Let's take a look at this code in IL form using ILDASM:

IL_0000: ldc.i4.0
IL_0001: stloc.0
IL_0002: ldc.i4.0
IL_0003: stloc.0
IL_0004: ldloc.0
IL_0005: box                [mscorlib]System.Int32
IL_000a: ldloc.1
IL_000b: box                [mscorlib]System.Int32
IL_0010: call               void [nunit.framework]NUnit.Framework.Assert::AreSame(object, object)
IL_0015: ret

Don't worry about some of the gobbly-gook here, but pay attention to IL_0005 where it calls box. This is known as boxing in .NET and basically converts value types to objects and vice-versa. For more information on boxing check out this article.

So we know it's loading our integer values but then boxes them into Int32 object types. So when the compare is done, we're comparing two new Int32 objects against each other. While their values are the same, the objects are not (for example every object has a ReferenceEquals method which will return a unique reference). Since we have two different boxed objects here, they are not the same. Strings on the other hand are not boxed so that test works (more on this later).

The obvious thing we might do is to change the test to use Int32 types instead of int. This will eliminate the boxing problem. Unfortuantely, again, this won't work because we're creating two different objects and the AreSame method is expecting to fail if the entire object is different, not just the value.

So the rule of thumb here is use AreEqual if you're just comparing values, use AreSame if you really want to compare things at the object level (and you can create two objects that are the same).

One thing that might be bugging you is the first test with the strings. Surely .NET boxed the strings into a native object so why didn't it fail? If you create this test (and look at the IL code generated) you'll see that strings are reference types already (plus the fact that they're immutable) and thus will produce duplicate items (at the object level). There's a good article here on strings in .NET that you may want to check out. Also you can use "String" and "string" interchangable, as one is just an alias for another. 

I know this post is basic and it may boil down to NUnit documentation saying "use AreEqual for values, use AreSame for objects" which you can find here but this was a bit of a mystery to some people so I thought I would elaborate here.

2 Comments

  • I don't think the following statement is correct:



    "The obvious thing we might do is to change the test to use Int32 types instead of int."



    In C#, int is simply an alias for System.Int32. Using Int32 instead of int should result in exactly the same box instruction. The type token used in the box instruction indicates the type being boxed, not what it's being boxed to. This is important because when unboxing, you must unbox to the type that was boxed.

  • Kevin,



    Thanks for the clarification.



    I was speaking from the perspective of where the people I was working with wouldn't know a) that int is an alias for System.Int32 and b) the IL code shows what's being boxed not what it's being boxed to, as you said.



    Your point on boxing makes this clearer.

Comments have been disabled for this content.