2017-10-11

Unittesting custom Sitecore SXA components using FakeDb part 2: Mocking and testing

In part 1, I discussed services necessary to get your repository unittests to work if you use ModelRepository.FillBaseProperties() in your code. In this part, I'll show how to mock these services and round up to demonstrate a working test.

Mocking Sitecore services

Using NSubstitute, we can easily create mocks for the necessary service interfaces to use in the mock ServiceCollection. IRendering and IPageContext :

private static IRendering MockRendering(Db fakeDb)
{
 var renderingMock = Substitute.For<IRendering>();
 renderingMock.DataSourceItem.Returns(fakeDb.GetItem(ItemId));
 return renderingMock;
}

private static IPageContext MockPageContext(Db fakeDb)
{
 var contextMock = Substitute.For<IPageContext>();
 contextMock.Current.Returns(fakeDb.GetItem(ItemId));
 return contextMock;
}

private static IControlEditabilityService MockIControlEditabilityService()
{
 var mock = Substitute.For<IControlEditabilityService>();
 var editabilityMock = Substitute.For<IControlEditability>();
 editabilityMock.Editable.Returns(false);
 mock.GetControlEditability(Arg.Any<IRendering>()).Returns(editabilityMock);
 return mock;
}

Note that both IRendering and IPageContext get the item you're going to mock per unittest and that's already in the FakeDb. The items they return should be the same and be easily specifyable in each TestMethod, so I've added this line to my test baseclass:

protected static ID ItemId = ID.NewID;

This ID is then used to create, add and retrieve a DbItem to and from the FakeDb. This same item also needs to be set as the current item in the Context:

Context.Items["sc_CurrentItem"] = fakeDb.GetItem(ItemId);

Usage in testcases

Because all the mocking work needed to run the tests is done in a baseclass, all you need in unit-specific test class is a way to get your repository (I've made the baseclass using generics so it automatically registers IMyService with MyService as implementation class in the ServiceCollection):

private MyRepository Repository
{
 get
 {
  return ServiceLocator.Current.ServiceProvider.GetService(typeof(IMyRepository)) as MyRepository;
 }
}

and, of course, tying it all up in a testcase, where SetupServices is described in the previous post:

[TestMethod]
public void FillExtraProperties_FillsTitle()
{
 using (var fakeDb = new Db())
 {
  // Arrange
  var item = new DbItem("MyItemName", ItemId, SitecoreItemConstants.TemplateId) {
   { SitecoreItemConstants.TitleFieldSelector, "testtitel" }
  };
  fakeDb.Add(item);
  SetupServices(fakeDb).FinalizeSetup();

  // Act
  var result = Repository.GetModel() as MykModel;

  // Assert
  Assert.IsNotNull(result);
  Assert.AreEqual("testtitel", result.MyTitle);
 }
}

And there you have it! If there's anything still unclear with the process don't hesitate to drop a comment. Happy testing!

2017-09-28

Unittesting custom Sitecore SXA components using FakeDb part 1: Setting up

When you create custom components for Sitecore, you're probably doing so because you've got a piece of logic for rendering certain information that simply isn't provided out-of-the-box. But proving (read: unit testing) that logic can be a bit tricky when everything in your component hooks into the Sitecore API.

Frameworks

For this article, I'm using NSubstitute but this method would work just as well with your mocking framework of choice.

The most important part however is FakeDb. FakeDb allows you to mock items in the Sitecore master database to test your repository and controller.

Mocking the Sitecore context

When you make a custom Sitecore component, there's a lot of "magic" going on in the background.  You can get the current URL, the data item that was configured for the component, even query everything else on the site if you really want to, for some reason.

This magic however also makes for tricky unittesting, because you aren't actually IN Sitecore when running your tests (you can be, by the way, but that would make your tests more like integration tests and that's a whole other part of the testing pyramid).

This post is based off examples shown in a StackOverflow post, but those examples are using the Sitecore.DependencyInjection namespace. When you make SXA components however, you're going to need the Sitecore.XA.Foundation.IoC namespace which behaves similar but slightly, infuriatingly, different.

In the steps below we'll set up unit testing for a model repository.

1. Set up your references

Most of the assembly references you need can be pulled off the official Sitecore NPM feed, but you'll need three extra;
  • Sitecore.XA.Foundation.Common
  • Sitecore.XA.Foundation.Editing
  • Sitecore.XA.Foundation.IoC
These can be found in your Sitecore Website\bin folder.
Other assemblies you'll need that can be obtained from NPM are
  • Sitecore.FakeDb
  • Sitecore.Kernel
  • Sitecore.Analytics
  • Sitecore.Analytics.Core
  • Sitecore.Analytics.Model
  • Sitecore.Logging
  • Sitecore.Mvc
  • Sitecore.Nexus
  • NSubstitute (or your choice of mocking framework, but I'll be using this in my examples)

2. Set up dependency injection

I suggest creating a base test class that your unit test classes derive from so you can make most of this plumbing just once. To be able to call FillBaseProperties from the ModelRepository base class your repository likely extends, this is the minimum amount of services you need to set up:

protected virtual void SetupServices(Db fakeDb)
{
    var mockServiceCollection = new ServiceCollection();
    new Sitecore.DependencyInjection.DefaultSitecoreServicesConfigurator().Configure(mockServiceCollection);

    mockServiceCollection.AddTransient(provider => MockRendering(fakeDb));
    mockServiceCollection.AddTransient(provider => MockPageContext(fakeDb));
    mockServiceCollection.AddTransient(provider => MockIControlEditabilityService());
    mockServiceCollection.AddTransient<IMyRepository, MyRepository>();

    var serviceProvider = mockServiceCollection.BuildServiceProvider();
    ServiceLocator.SetLocatorProvider(new Locator(serviceProvider));
    
    var instance = typeof(FieldTypeManager).GetField("Instance", BindingFlags.Static | BindingFlags.NonPublic);
    var instanceValue = instance.GetValue(null) as Sitecore.DependencyInjection.LazyResetable<Sitecore.Abstractions.BaseFieldTypeManager>;
    instanceValue.Value.Initialize();
}

3. Mocking

In the next post I'll go into mocking your services and items.

2017-08-30

Configure SOLR search with Sitecore Experience Accelerator

It's been quite a while since my last update, but today I finally was able to get something to work that gave me so much headache I thought it might help someone else if I'd put my solution in a blog.

The problem was configuring Solr search for our new Sitecore site. Normally, this is supported pretty much out of the box. However this new site also uses the awesome new Experience Accelerator, which throws a wrench into the process. It also doesn't help I wasn't able to find a single blog or Sitecore documentation site that gave a proper step-by-step guide for setting up Solr so I'll also do that here.

1. Setting up Solr itself


Grabbing the Solr package

First, we need to get Solr running. For this, I used a BitNami package. Sadly, Sitecore recommends an older version of Solr (5.1) so you'll have to dig for the right package version since BitNami doesn't maintian an archive page for its packages. Luckily, I've done that for you.

Don't install it just yet though, we'll automate all this in the next step!

SolrCannon

Kam Figy (Kamsar on Github) has created an awesome Powershell script to install Solr , configure it for your Sitecore installation and even generate a config patch file to get Sitecore to use the correct Solr cores. Grab it!

Have a close look at the # Variables section and change everything to your liking.

Schema.xml

Lastly, Solr needs a schema.xml to operate on. This is the only step that will require some manual labor because Sitecore doesn't export a correct schema.xml for indexing your SXA content.

  • First, export one from Sitecore (/sitecore/client/Applications/ControlPanel.aspx -> Generate the Solr Schema.xml file)
  • Next, open the schema.xml and add the following line near the bottom. This will make sure your SXA content is also indexed:
<dynamicField name="*_tm"
    type="text_general"
    indexed="true"
    stored="true"
    multiValued="true"/>

Fire all cannons!

Now put everything from the last steps together in one folder. 

Run SolrCannon and answer "y" to the prompt. This will automatically install the BitNami package and fully configure it with everything you need.

2. Configure Sitecore

Next, Sitecore needs to know we're using Solr.

SolrSwap

Another great script, created by Patrick Perrone, automatically switches existing Sitecore config patch files to Solr.

Run it and enter the root folder of your Sitecore instance. Next, answer "S" to have it switch Sitecore over from the standard Lucene to Solr.

You may have to manually enable [instance root]\Website\App_Config\Include\z.Foundation.Overrides\Sitecore.XA.Foundation.Search.Solr.config and disable the Lucene config.

Solr.config

After running SolrCannon, it generated a file called Solr.config. Put this in [instance root]\Website\App_Config\Include\z.yourfolder. The name of the folder can be to your liking, but it MUST start with z and be alphabetically after z.Foundation.Overrides. This is to ensure this config patch is loaded at the very last by Sitecore.

If you did this step incorrectly you'll recognize it by Sitecore throwing the following error:

Could not create instance of type: Sitecore.ContentSearch.SolrProvider.SwitchOnRebuildSolrSearchIndex. No matching constructor was found.


3. Rebuild your indexes and check for errors

If you did everything correctly, when you rebuild your indexes (/sitecore/client/Applications/ControlPanel.aspx -> Indexing manager -> Rebuild) you should see no errors in the Solr logs and all indexes are rebuilt successfully. 

If Solr logs errors, you can add the missing fields to the schema.xml. Running SolrCannon won't update this in the Solr config folder, so find the schema.xml in [Solr install root]\apache-solr\solr\configsets\[sitename]_configs\conf and replace it with the new schema.xml.

Don't forget to restart the solrApache and solrJetty services or the new schema.xml won't be loaded. Rebuild your indexes.

If all went well your indexing manager window should look like this, with document counts > 0:

Indexing manager success

4. ...?

5. Profit!

Now add a search bar and search results page to your site and you're done!