Unit testing Sitecore MVC? It’s easy when using Synthesis

I have been using Sitecore for a while now, and one of the nice features of the Sitecore API is that it is flexible in presenting field values.

string title = Sitecore.Context.Item["title"];

No matter what type of template you use, if it has a title field, this line of code will work. In fact, this code works even if the template does not have a title field.

Unit testing sitecore using MVC and Synthesis ORM

For a while I have switched from Webforms to MVC. This makes me more conscious of the templates I use, because I now pass a strongly typed model from the controller to the view. I wanted to try an ORM and decide to go with Kam Figy’s Synthesis. It easy to configure, it autogenerates interfaces and concrete classes and it is easily unit testable.

Synthesis generates interfaces and concrete classes based on the templates you include. It is configurable to in- or exclude templates and fields. It is easily installed using Nuget packages and has a Synthesis.Testing package that contains dummy field type implementations to help you write cleaner mocks of Synthesis item types.

After reading Martina Welander’s post on Unit Testing Sitecore MVC we decided to use this as a starting point and combine this with the Synthesis ORM we are using.

Interfaces

First we created interfaces for the parts of the Sitecore.Context we use regularly.

public interface ISiteContextAdapter
{
  IStandardTemplateItem RootItem { get; }
}

public interface ISitecoreContext
{
  IStandardTemplateItem CurrentItem { get; }
  IStandardTemplateItem HomepageItem { get; }
  bool IsPageEditor { get; }
  bool IsPreview { get; }
  ISiteContextAdapter Site { get; }
  IDatabaseAdapter Database { get; }
  RenderingParameters Parameters { get; }
  IStandardTemplateItem DatasourceItem { get; }
  string Placeholder { get; }
}

Instead of returning an Item we chose to return an IStandardTemplateItem. This is the base interface implemented by all template interfaces generated with Synthesis. Instead of the Sitecore.Context.Database we used the IDatabaseAdapter that comes with Synthesis.

Concrete implementation

We created the SitecoreContext class that implements the ISitecoreContext interface. An object of this type is injected in every controller constructor using Dependency Injection. This is where we hook up the coupling with the Sitecore.Context.

public class SiteContextAdapter : ISiteContextAdapter
{
  private readonly SiteContext siteContext;
  private readonly IDatabaseAdapter databaseAdapter;

  public SiteContextAdapter(SiteContext siteContext, IDatabaseAdapter databaseAdapter)
  {
    this.siteContext = siteContext;
    this.databaseAdapter = databaseAdapter;
  }

  public IStandardTemplateItem RootItem{
    get{
      return this.databaseAdapter.GetItem(this.siteContext.RootPath);
    }
  }
}

public class SitecoreContext : ISitecoreContext
{
  private RenderingContext rendering = RenderingContext.CurrentOrNull;

  public IStandardTemplateItem CurrentItem{
    get{
      return Sitecore.Context.Item.AsStronglyTyped();
    }
  }

  public IDatabaseAdapter Database{
    get{
      return new DatabaseAdapter(Sitecore.Context.Database);
    }
  }

  public IStandardTemplateItem DatasourceItem{
    get{
      if (this.rendering != null && this.rendering.Rendering.Item != null)
      {
        return this.rendering.Rendering.Item.AsStronglyTyped();
      }
      return null;
    }
  }

  public IStandardTemplateItem HomepageItem{
    get{
      return this.Database.GetItem(Sitecore.Context.Site.StartPath);
    }
  }

  public ISiteContextAdapter Site{
    get{
      return new SiteContextAdapter(Sitecore.Context.Site, this.Database);
    }
  }

  public bool IsPageEditor{
    get{
      return Sitecore.Context.PageMode.IsPageEditor;
    }
  }

  public bool IsPreview{
    get{
      return Sitecore.Context.PageMode.IsPreview;
    }
  }

  public RenderingParameters Parameters{
    get{
      return this.rendering.Rendering.Parameters;
    }
  }

  public string Placeholder{
    get{
      if (this.rendering != null && this.rendering.Rendering != null)
      {
        return this.rendering.Rendering.Placeholder;
      }
      return null;
    }
  }
}

Fakes implementation

To be able to unit test we also create fake classes to use in our unit tests.

public class FakeSiteContextAdapter : ISiteContextAdapter
{
  public FakeSiteContextAdapter(IStandardTemplateItem rootItem = null)
  {
    this.RootItem = rootItem;
  }

  public IStandardTemplateItem RootItem { get; set; }
}

public class FakeSitecoreContext : ISitecoreContext
{
  public FakeSitecoreContext(IStandardTemplateItem current = null, IStandardTemplateItem home = null, IStandardTemplateItem datasourceItem = null, bool isPageEditor = false, bool isPreview = false, RenderingParameters parameters = null, IDatabaseAdapter database = null, string placeholder = null, ISiteContextAdapter site = null)
  {
    this.CurrentItem = current;
    this.HomepageItem = home;
    this.IsPageEditor = isPageEditor;
    this.IsPreview = isPreview;
    this.Database = database;
    this.DatasourceItem = datasourceItem;
    this.Parameters = parameters;
    this.Placeholder = placeholder;
    this.Site = site;
  }

  public IStandardTemplateItem CurrentItem { get; set; }
  public IDatabaseAdapter Database { get; set; }
  public IStandardTemplateItem DatasourceItem { get; set; }
  public IStandardTemplateItem HomepageItem { get; set; }
  public bool IsPageEditor { get; set; }
  public bool IsPreview { get; set; }
  public RenderingParameters Parameters { get; set; }
  public string Placeholder { get; set; }
  public ISiteContextAdapter Site { get; set; }
}

Implementation Unit Test

Now we are going to write a unit test using the FakeSitecoreContext. This unit test tests the controller action that provides an actionresult of a view that takes a model containing a MetaTitle. We are passing in a mocked item of the MetaInformation template.

[TestMethod]
public void HeaderMetaTitleProvidedTest()
{
  string metaTitle = "My meta title";

  // create a mock item
  var metaInformationItem = new Mock<IMetaInformationItem>();
  metaInformationItem.SetupGet(x => x.MetaTitle).Returns(new TestTextField(metaTitle));

  // setup the fake context
  FakeSitecoreContext sitecoreContext = new FakeSitecoreContext() { CurrentItem = metaInformationItem };

  TagsController controller = new TagsController(sitecoreContext);
  var result = controller.MetaTags() as ViewResult;
  IMetaInformationItem model = result.Model as IMetaInformationItem;

  Assert.IsNotNull(controller);
  Assert.IsNotNull(model);
  Assert.IsTrue(model.MetaTitle.RawValue.Equals(metaTitle));
}

Controller implementation

Al we have to do now is make our unit test pass by actually writing the controller. In the controller we use an object that implements the ISitecoreContext interface. So no tight coupling with the Sitecore.Context. An object of type SitecoreContext is injected using Dependency Injection. Also we use a model that implements the IMetaInformationItem interface generated by Synthesis. This interface is based on the MetaInformation template. This helps us in the view since we have a strongly typed object with all its properties.

public class TagsController : Controller
{
  private ISitecoreContext sitecoreContext;

  public TagsController(ISitecoreContext sitecoreContext)
  {
    this.sitecoreContext = sitecoreContext;
  }

  public ViewResult MetaTags()
  {
    IMetaInformationItem model = sitecoreContext.CurrentItem as IMetaInformationItem;
    return View(model);
  }
}

Result

With the wrapping of the Sitecore.Context and the Dependency Injection in place we can now unit test the results our controller actions give us. We can use the TestFields provided with Synthesis to easily mock our items. We can now develop faster, correct code. Unit testing sitecore  can improve the quality of your code. I wouldn’t have it any other way.

One comment

  1. Hi Martjin, thanks for writing such a nice descriptive blog about Unit Testing. I followed the blog and created Unit Tests using Synthesis, NUnit and Moq. In the Test method that is mentioned in the blog, I got a type cast error in this line:
    // setup the fake context
    FakeSitecoreContext sitecoreContext = new FakeSitecoreContext() { CurrentItem = metaInformationItem };
    So, I changed this line to below line

    FakeSitecoreContext sitecoreContext = new FakeSitecoreContext() { CurrentItem = metaInformationItem as IStandardTemplateItem};

    Now the type casting error is not appearing but the CurrentItem becomes null and the test is failing in the Assert lines.
    Could you please suggest what could have gone wrong?

Leave a Reply

Your email address will not be published. Required fields are marked *