Recently, I've been trying to improve the unit testing on my Sitecore projects. Since Sitecore code is often very intimately tied to the data in the content tree, it made sense to leverage FakeDb to achieve true unit tests, rather than integration tests. Since my project is also using Glass as an ORM, this adds another wrinkle that necessitates thorough testing. Adding Fluent Assertions to my tests also provided a great way to describe the expected outcomes of my tests.
I started with a rather simple test, but started noticing that while my test was quick and returned the expected results when testing valid data, it timed out with an OutOfMemoryException if passed invalid data. On valid data tests, I was seeing less than 100 meg of memory for the testing process and very light CPU load for a few seconds. On invalid data tests, the process was grabbing almost a gig of memory and pushing the processor to more than 50% utilization for about 3-4minutes before hitting the OutOfMemoryException.
Luckily, I was able to get some hints from the stack trace, which implied the issue was happening in one of the libraries I was using, rather than my own. So I firedup the debugger and took a look. Using Visual Studio's Diagnostic Tools window, it was easy to see that my code wasn't causing the memory or timeout issues. However, I noticed that the last line of my test, the Fluent Assertions call, was where the code finally died. Here was my assertion:
Since my Fluent Assertions code was checking a FakeDb item and code that performed a GlassCast, that narrowed down where the issue might lie. Thankfully, Fluent Assertions is open source, so I cloned the version my project depended upon and started digging through the code in the debugger.
What I noticed was that Fluent Assertions uses reflection to pull all public properties of an object and then use those to output a message about the object you have have expectations about. As an experienced Sitecore developer, I'm sure you can already see where this is going.
Sitecore.Data.Items.Item is a fairly big object, with a lot of other objects as properties, including references to other Items. Since the reflection done by Fluent Assertions is recursive, we are crawling way too much to display a simple message saying why a test failed, even for objects that came from FakeDb, rather than the actual Sitecore content tree.
Fortunately, Fluent Assertions has a check in its ToString() method to determine if this reflection needs to take place. Here's the check and the reflection call:
Here we can see that the recursive reflection calls only happen if the object we are validating expectations about has not implemented a custom ToString() method.
For custom objects that just contain an Item or a Glass item as a property, you can implement your own ToString() method that pulls any relevant data out of the property, rather than letting Fluent Assertions try to pull ALL of that data via reflection.
Unfortunately, if you are using a Sitecore.Data.Items.Item or a Sitecore.FakeDb.Data.Items.ItemWrapper, you are dealing with code you didn't write, so you can't simply add a ToString() method. You also can't use an extension method, because extension methods are only used if an instance method isn't found. Since object already has a ToString() method, this prevents us from using this technique.
Luckily, we can just extend Fluent Assertions by adding our own custom Formatter that knows how to handle those two classes.
Finally, you need to register your custom formatter in your test setup.
Now if you have any Fluent Assertions based on Sitecore items, your tests will perform as expected. You can likewise expand this technique to handle your Glass items rather easily, but your implementation will vary based on the templates you used to generate your Glass items.
A journey of learning Sitecore, from the viewpoint of a SharePoint professional.
Thursday, November 5, 2015
Fluent Assertions FormatException when trying to include a Sitecore.Data.ID in your test failure messages
Recently, when using Fluent Assertions with my unit testing of a Sitecore project, I was attempting to display a friendly message when a block of code matched a Sitecore item that did not have a specific template ID.
My assertions looked like this:
Unfortunately, when running the test, I was getting an exception, but it was from FluentAssertions, rather than my code.
If I ran the test without attaching a debugger, my exception was "Input string was not in a correct format".
If I ran the test with a debugger attached, the exception was "Index (zero based) must be greater than or equal to zero and less than the size of the argument list."
Fortunately, when I attached the debugger, I could see that the message itself was fine, but Fluent Assertions was unable to handle it. Examining the value of message revealed the likely culprit.
String.Format uses curly braces to denote substitutions. Those curly braces are coming from the Sitecore.Data.ID implemention of ToString(), which looks like this:
After checking the documentation for the different format parameters for Guid.ToString(), we can see that passing the "B" argument to Guid.ToString() is generating the curly braces. Fortunately, the default ToString() method of Guid uses the "D" parameter, rather than the "B" parameter. This will output our Guid without curly braces.
With a very minor change to how we pass the ID to Fluent Asssertions, we can bypass ID.ToString() and get a message that will work with Fluent Assertions.
After making this change, my tests began outputting messages as expected.
My assertions looked like this:
Unfortunately, when running the test, I was getting an exception, but it was from FluentAssertions, rather than my code.
If I ran the test without attaching a debugger, my exception was "Input string was not in a correct format".
Fortunately, when I attached the debugger, I could see that the message itself was fine, but Fluent Assertions was unable to handle it. Examining the value of message revealed the likely culprit.
String.Format uses curly braces to denote substitutions. Those curly braces are coming from the Sitecore.Data.ID implemention of ToString(), which looks like this:
After checking the documentation for the different format parameters for Guid.ToString(), we can see that passing the "B" argument to Guid.ToString() is generating the curly braces. Fortunately, the default ToString() method of Guid uses the "D" parameter, rather than the "B" parameter. This will output our Guid without curly braces.
With a very minor change to how we pass the ID to Fluent Asssertions, we can bypass ID.ToString() and get a message that will work with Fluent Assertions.
After making this change, my tests began outputting messages as expected.
Subscribe to:
Posts (Atom)