Sitecore.FakeDb – code generation with Sitecore.FakeDb.Construct

If you ever tried to write unit tests for your Sitecore solution, you should be aware of Sitecore.FakeDb unit testing framework. Sitecore.FakeDb is great! You can find lots of tutorials and examples right on GitHub Wiki here.
The problem I faced is that in real-world scenarios I normally need to mimic the template structure I have on my solution before to actually setup the test items I am using for the test. The example of constructing db templates can be found here and here.

Purpose

The thing is that I want my db templates in unit tests I’m using to always match my real templates in my solution. Meaning that every time I update template in Sitecore, I should update my db templates in unit tests. The same about standard values that FakeDb fully supports (example is here). With Sitecore.FakeDb, there is an option to set the serialization folder and map it to serialized templates and items (tutorial is here)
But to be honest, this is still not too great way of doing it as my unit tests depend on external files. I would really prefer that my unit tests are just code. In addition, the serialized folder is not a visible thing while working with items as well as it makes unit tests hard to read and understand.
So I was thinking what if create a tool that can use generated code and construct the Db object with those db templates as well as their standard values…

Sitecore.FakeDb.Construct

I have implemented a simple project that allows you to create types that define Sitecore.FakeDb DbTemplate fields as well as standard values and then construct the Db object using them. In result you should be able to generate those construct-able types with t4 templates (for example with TDS code generation) and then use those in your unit tests, saving your time on replicating and supporting the template structure.

It is available on nuget:

Install-Package Sitecore.FakeDb.Construct

This package installs one dll which has base objects and the factory implementation that can construct the db templates and their standard values.
Let’s get to code now. The db template that you can create (code-generate) should be inherited from the abstract class

Sitecore.FakeDb.Construct.ConstructableDbTemplate

and implement one abstract method

protected abstract void ConstructTemplate(Db db, ConstructableStandardValues standardValues);

In this method you have access to db object which is being constructed as well as standard values object, description of which is a bit below. And what you need to do is to define fields for the template being constructed as well as base template IDs.
For example, the type for most common template of Navigation data template can look like this:

public class NavigationDbTemplate : ConstructableDbTemplate
{
	protected override void ConstructTemplate(Db db, ConstructableStandardValues standardValues)
	{
		// Setup template fields and base template IDs in this method.
		var template = new DbTemplate(TemplateName, TemplateId)
		{
			{ new DbField(FieldNames.HideFromNavigation, FieldIds.HideFromNavigation), standardValues[FieldIds.HideFromNavigation] },
			{ new DbField(FieldNames.NavigationTitle, FieldIds.NavigationTitle), standardValues[FieldIds.NavigationTitle] },

		};

		template.BaseIDs = new ID[]
		{
		};

		db.Add(template);
	}

	public static ID TemplateId = new ID("{F16CDC22-CF07-4688-B864-5825FA424C0E}");
	public const string TemplateName = "Navigation";

	public static class FieldNames
	{
		public const string HideFromNavigation = "Hide From Navigation";
		public const string NavigationTitle = "Navigation Title";
	}

	public static class FieldIds
	{
		public static ID HideFromNavigation = new ID("{3DB7E755-F283-43E1-BA89-084FC73B272A}");
		public static ID NavigationTitle = new ID("{3E805864-4A2B-4394-A2A4-99D1CF97ACA8}");
	}
}

In addition, you can create a type which is responsible for setting the standard values for your db template. This type should be inherited from

Sitecore.FakeDb.Construct.ConstructableStandardValues<T>

abstract class and implement one abstract method that should return a dictionary of field ID and its standard value:

protected abstract IDictionary<ID, string> ConstructStandardValues();

For example, the type for most common standard values of Navigation data template can look like this:

public class NavigationDbTemplateStandardValues : ConstructableStandardValues<NavigationDbTemplate>
{
	protected override IDictionary<ID, string> ConstructStandardValues()
	{
		return new Dictionary<ID, string>
		{
			{ NavigationDbTemplate.FieldIds.HideFromNavigation, "" },
			{ NavigationDbTemplate.FieldIds.NavigationTitle, "$name" },
		};
	}
}

The generic type parameter is the type of target ConstructableDbTemplate that standard values are applicable to.

Alright, this is how db template types should look like (should be code-generated). Now its time to see how we can apply them. There is a type

Sitecore.FakeDb.Construct.DbConstructFactory

that provides few methods that can fill the Db object. So the unit test for my navigation template and standard values can look like this:

[Test]
public void ConstructDb_NavigationTemplateAndStandardValues_ConstructsDbInstance()
{
	using (var db = DbConstructFactory.ConstructDb(
		new[] { new NavigationDbTemplate() }, 
		new[] { new NavigationDbTemplateStandardValues() }))
	{
		// setup
		var currentItemId = ID.NewID;
		var navRootItem = new DbItem("navigation item", currentItemId, NavigationDbTemplate.TemplateId);

		db.Add(navRootItem);

		// act
		var currentItem = db.GetItem(currentItemId);

		// assert
		currentItem.Should().NotBeNull();
		currentItem[NavigationDbTemplate.FieldNames.NavigationTitle].Should().BeEquivalentTo("navigation item");
	}
}

There are 2 overloads for ConstructDb method:

public static Db ConstructDb(
	IEnumerable<ConstructableDbTemplate> templates, 
	IEnumerable<ConstructableStandardValues> standardValues);
	
public static Db ConstructDb(Assembly assembly);

So the unit test above can just pass the executing assembly and all db templates and standard values in it will be constructed into the Db object:

[Test]
public void ConstructDb_NavigationTemplateAndStandardValues_ConstructsDbInstance()
{
	using (var db = DbConstructFactory.ConstructDb(Assembly.GetExecutingAssembly()))
	{
		// setup
		...
		// act
		...
		// assert
		...
	}
}

Easy!

TDS code generation

The nuget package ships t4 templates for TDS code generation as well. Once installed, you can find those in src folder:

sitecore-fakedb-construct-ttemplates

More about how to use TDS code generation could be found here https://www.hhogdev.com/help/tds/propcodegen

Have a look at generated code for my example templates here. You can see that template inheritance is properly implemented and the field IDs and field names constants are being populated.

Important note about code generation for standard values part. By default, TDS SitecoreItem objec which is being used while code-gen, does not pull all field values from the item. And from what I researched, there is no way to tell TDS “please include all field values while code generation”. Instead, they offer to add field names to the list on project properties in the “Code Generation” tab as shown here https://www.hhogdev.com/help/tds/propcodegen.
It is logical that pulling all field values is a dangerous thing as there could be really lots of them on the item in theory. But would be really nice to have an option to include them on demand.

As always, the source code is on my GitHub https://github.com/vhil/Sitecore.FakeDb.Construct. I also tried to unit-test it and I am actively using that on my projects.

Thanks guys, time to unit test now!

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s