LinkItemCollection overrides Equals in CMS 6 R2

Some background

After an upgrade from CMS 6 R1 to R2 a few unit tests started to fail. After some detective work it seems the common theme of the failing tests were that they involved LinkItemCollection. One test for instance had the following setup in somewhat simplified code:

protected LinkItemCollection CreateLinkCollection<T>(params T[] pages) where T : PageData
{
    var items = new LinkItemCollection();
    Using<IDataFactory>().Stub(f => f.GetPages(items)).Return(pages);
    return items;
}

page.RegionalLinks = CreateLinkCollection<SomePage>(new[] { SomePage1, SomePage2, SomePage3 });
page.NationalLinks = CreateLinkCollection<SomePage>(new[] { OtherPage1, OtherPage2, OtherPage3 });

If you’re not familiar with a mocking framework (we’re using Rhino Mocks) line 4 might be somewhat confusing. What happens here is that we tell our mocked datafactory that if you call the method GetPages with the argument items (which is initialized as a new LinkItemCollection on the line above) it will return the collection of pages sent in as argument to the method. The collection is then returned and stored on an EPiServer page. This mocking will make sure that when we for instance call DataFactory.GetPages(RegionalLinks) it will return the SomePages array and DataFactory.GetPages(NationalLinks) will return OtherPage array. Or at least they did before the upgrade to R2.

Behavior after the upgrade

For some reason the mocking of the datafactory seemed to return the same collection of pages regardless of what LinkItemCollection was sent in. This lead me to believe that Rhino Mocks thought that the LinkItemCollections were the same object. Since we new this object up in the method CreateLinkCollection this was quite surprising. So again, a decompiler which was used to compare the LinkItemCollection between R1 and R2 came to the rescue.

So, what I discovered is that in R2 the LinkItemCollection object overrides the Equals method and implements it as such

public override bool Equals(object obj)
{
	LinkItemCollection linkItemCollection = obj as LinkItemCollection;
	if (linkItemCollection == null || this.Count != linkItemCollection.Count)
	{
		return false;
	}
	for (int i = 0; i < this.Count; i++)
	{
		if (this[i].ToMappedLink() != linkItemCollection[i].ToMappedLink())
		{
			return false;
		}
	}
	return true;
}

Without going into too much details about Rhino Mocks this explains why the same collection of pages is returned since instead of checking that the reference is pointing to the object sent in it compares length and ToMappedLink urls. Since our page collections both contains 3 elements and ToMappedLink both will be empty in our mocked implementations the objects will be considered equal.

To solve this problem I simply changed the Rhino Mocks setup to once again check that the objects are the same in a reference sense (eg, the behavior it had in R1)

var linkItemCollection = new LinkItemCollection();
using<IDataFactory>().Stub(f => f.GetPages(Arg<LinkItemCollection>.Is.Same(links))).Return(pages);

On a side note…

I do like the change as a whole. What would have been nice if EPiServer could release a report (generated from http://inca-app.com/ for instance) so you could easily browse what has changed between the assembly versions.

  • http://twitter.com/robrawbert Robert Andersson

    Nice blog post!
    I also didn’t know about http://inca-app.com/ until you mentioned it here. Seems like an awesome tool, thx!

  • Fredrik Tjärnberg

    Hi Stefan,

    http://inca-app.com/ was new to me as well. We have been running an internal tool for a couple of years now to trap binary breaking changes. This tool fails our build if an API is found to be removed in which case we either fix the problem or acknowledge it. All acknowledged binary breaking changes should be found in the release notes document. I will post our complete report on world.episerver.com shortly. It will only show signature diffs not semantic changes as in this case. Will look into inca to see if that is something to use for future releases.