Unit testing Umbraco 7

I’ve implemented the wonderful Hybrid Framework for Umbraco and got it upgraded to Umbraco 7. All good. But…as the site continues to grow and gets more complex, I’m feeling more and more scared of doing big development. This post looks at how I set up unit testing using NUnit, Microsoft Fakes and Moq. I used help from Andy Butlands post on using Microsoft Fakes, so kudos there!

One issue I was having that was causing problems was that my controllers were very fat. While unit testing controllers is perfectly do-able, its easier to separate out the functionality into utility and service classes that can easily be instantiated from tests.

Getting UmbracoContext and CurrentPage

One of the most common things being done in the utility classes is looking up content and getting properties of the current page. So, we need to get an UmbracoHelper object and have access to the CurrentPage object. From the controller action, this can be done pretty easily…

public class LCCStandardContentPageController : BaseSurfaceController
{
 ...
 public ActionResult LCCStandardContentPage()
 {
  pageHelper = new ContentPageHelper(UmbracoContext, CurrentPage);
  ...

However, how to get a test UmbracoContext and dummy ‘CurrentPage?’

Umbraco.tests.dll

This can be grabbed by getting the Umbraco source, compiling it and then referencing the Umbraco.tests.dll from the unit tests project. In addition to this, the test configuration files must be in the test project, or it won’t work.

Because I’m really nice, you can grab all the necessary test files here. The included files are from a 7.2.0 build.

Setting up a test object

So, I have a class called ‘ContentPageHelper.’ Its expecting to receive an UmbracoContext object and an IPublishedContent object. To get the UmbracoContext, I did the following…

private UmbracoContext GetUmbracoContext()
{
 var routingContext = GetRoutingContext("/test");
 return routingContext.UmbracoContext;
}

GetRoutingContext is a method of the BaseRoutingTest class. UmbracoContext is a property of resultant object.

To get the CurrentPage object, I used Moq (added through Nuget package manager), like so…

private IPublishedContent MockIPublishedContent(int id = 1000, string name = "pc")
{
 var mock = new Mock<IPublishedContent>();
 mock.Setup(x => x.Id).Returns(id);
 mock.Setup(x => x.Name).Returns(name);
 return mock.Object;
}

Testing extension methods

You may notice that there is no line in the above method like so…

mock.Setup(x => x.GetPropertyValue<string>("title")).Returns("name");

This is because extension methods cannot be mocked. So, the way round this is to use Microsoft Fakes. By right clicking on a reference from the test project, the option to ‘add fakes assembly’ appears. This allows us to create shims and stubs for methods/properties of any class library.

So, to create a shim for the ‘GetPropertyValue<string>’ (notice the type-cast), I added a fake assembly for Umbraco.Core, and then…

using (ShimsContext.Create())
{
Umbraco.Web.Fakes.ShimPublishedContentExtensions.GetPropertyValueOf1IPublishedContentString<string>((doc, alias) =>
{
 return "name";
});
}

Notice, the shim will only work if its within the ShimsContext ‘using’ statement. So, for individual tests, known values can be returned to test against.

Shimming properties

In the hybrid framework, there is a utilities namespace that contains methods for getting DAMP (digibiz advanced media picker) objects, handling email etc etc. But, it has an ‘Umbraco’ property, which is actually an UmbracoHelper. To return a shim of this property whenever its called is…

Umbraco.Extensions.Utilities.Fakes.ShimExtensionMethods.UmbracoGet = () => new UmbracoHelper(GetUmbracoContext());

Notice, no parameters since its a property.

Conclusion

I feel quite a bit safer now, knowing I can automate testing on functionality I’ve developed. The ability to debug tests and actually see what mocks and shims are returning is also very useful. For my testing, I’m using NUnit with the nunit test adapter installed through NuGet.

The next step I think is to look at Coded UI tests. These allow functional tests on a user interface to be generated and automated. Only for Visual premium or ultimate though (damn you Microsoft! I guess there’s always Selenium).

Anyway, comments or ways to improve/optimise this would be most appreciated.

3 thoughts on “Unit testing Umbraco 7

  1. hey there,

    thank you posting this up – can’t tell you how much it’s helped! unit testing umbraco is proving to be a real challenge but we’re beginning to get somewhere…

    one thing we’re still hung up though is the ‘UmbracoContext’. we’ve tried the code:

    private UmbracoContext GetUmbracoContext()
    {
    var routingContext = GetRoutingContext(“/test”);
    return routingContext.UmbracoContext;
    }

    regardless of what we do we always get:

    System.ArgumentNullException: Value cannot be null.
    Parameter name: applicationContext

    debugging the code jumps into line 357 of the ‘BaseDatabaseFactoryTest.cs’ file in the umbraco tests project (which doesn’t help us much!).

    it *feels* like we’re missing something from the umbraco test project set up (we’ve copied the ‘Umbraco.Tests.dll’ and the required config files and set their ‘Copy to Output Directory’ value to ‘Copy always’) but still no dice…

    re those 3 config files (umbracoSettings.config, umbracoSettings.minimal.config and web.config) we’ve put them in ‘OurTestProject/Configurations/UmbracoSettings’ – based on other projects we’ve looked at, this is correct.

    any clues?! the ‘UmbracoContext’ seems to be the bit most people (oh, and us!) get hung up on but if we crack it, i’ve a feeling the rest of our umbraco projects are testable?

    any suggestions would be really appreciated!

    cheers,

    jake

  2. found it!

    did a lot of parallel stepping through code in visual studio with the umbraco test project on one side and ours on the other trying to figure out what was going wrong. line 356 in ‘Umbraco-CMS-dev-v7\src\Umbraco.Tests\TestHelpers\BaseDatabaseFactoryTest.cs’ was the key: in the umbraco project ‘ApplicationContext’ was populated, in ours it wasn’t.

    we traced that back to the set up and noticed it was using nunit in the Umbraco.Tests.dll…

    in my project i was using the ms test declarations ([TestClass] and [TestMethod]) so i changed them to [TestFixture] and [Test] and guess what?

    one populated UmbracoContext – happy days!

    now all i need to do is get my ms fakes to play nicely so i can shim out the ‘GetPropertyValue’ extension methods and i’m getting somewhere.

    happy (almost) days đŸ˜‰

Leave a Reply