What do you need to do to set up an Episerver CMS environment in a test environment so that you can run integration tests through the whole stack? It’s probably easier than you might believe, especially with some of the enhancements that were released in CMS 11.

In this post, I will walk you through an example of how you can setup the environment ready to run an API level integration test. With this, I refer to tests that verify the functionality from the service API level down to the database avoiding the complexity of the requests/response layer with the need for setting up a web server and making HTTP requests as part of the tests.

We will create a temporary database, start the Episerver CMS instance, apply an export package with a default structure, run the tests and finally tear down the instance and clear the database. While some of these parts might not apply to your environment or need changes to fit they can hopefully provide you with some ideas and guidance.

I will use xUnit.net as the testing framework in this example, but it should not be difficult to modify it to fit with whichever test framework that you are using.

In this example, we will only run the setup and teardown once for the whole test assembly, but it’s up to you if you want to run it more frequently.

The full source code used in this example can be found at https://github.com/hennys/integration-test-sample/.

Creating a temporary database

There are many ways to solve this problem and as I cannot claim to be an expert database administrator in any capacity I won’t attempt to claim that this is the best way to archive this.

The sample repository contains a simple helper class that can provide an example of how it can be done in a way that has worked successfully for us. Each time the test setup is run, a new database name is generated and then that database is created on the database server. This requires that the user who’s running the test has enough access rights to the database server, but this is usually not a problem in a development environment. The helper class wraps this all up in a disposable object that will remove the database when the instance is disposed. Usage is dead simple.

_database = DatabaseHelper.Temporary(“[connection string]”);

The database will be created empty and we will let the Episerver instance create the database schema itself during the initialization by using a configuration setting. Exactly how this is done is described in the section below.

Configuring the application

Just as your web application is configured using the web.config you can normally use a standard app.config file to configure the Episerver instance for your integration tests. Some test frameworks have a few quirks in this area so you may be happy to learn that we have made it much easier to configure Episerver from code in CMS 11. The easiest way to do this is by creating an initialization module and do your configuration in the ConfigureContainer method.

[InitializableModule]
[ModuleDependency(typeof(EPiServer.Web.InitializationModule))]
public class IntegrationTestInitialization : IConfigurableModule
{
    public void ConfigureContainer(ServiceConfigurationContext context)
    {
        context.Services.Configure<DataAccessOptions>(o =>
        {
            o.SetConnectionString("...");
            o.CreateDatabaseSchema = true;
        });
    }

    public void Initialize(InitializationEngine context) { }
    public void Uninitialize(InitializationEngine context) { }
}

While your application may require additional configuration, these two are the only ones required in our basic example. The first method, SetConnectionString will set the default connection string to our freshly created database and set CreateDatabaseSchema to true will ensure that the database schema is deployed when started.

Starting the Episerver instance

The next step is to start the Episerver instance. This is done by creating a new instance of the EPiServer.Framework.Initialization.InitializationEngine class and calling the Initialize method.

_engine = new InitializationEngine((IEnumerable)null, HostType.TestFramework, new AssemblyList(true).AllowedAssemblies);
_engine.Initialize();

The InitializationEngine class comes with several different constructors and it’s unfortunately quite important which one you use. This has historical reasons, but it’s something that we hope to be able to fix in a future release.

The one we will use takes three parameters, a module list, a host type and an assembly list. The module list allows the user to provide a list of which initialization modules that should be used and while it may be tempting to try to optimize this list I would strongly recommend passing in null here, which will signal the engine to run all modules it can find.

The next parameter is a HostType enumeration value, which in this case should be TestFramework. Providing this value will disable certain parts of the solution such as Scheduler and Remote events that can give you issues if run inside a test framework.

The final parameter is a list of assemblies that should be considered when scanning for types during the initialization. By using the AssemblyList class here we will get a behaviour that is identical to one used when initialization is started from a web application.

Setting up a test site

The next step is to ensure that a site is defined and that it contains the required content. This may or may not be required in all cases.

The first thing we want to do is to make sure that the correct language branches are enabled on the site. This can be done by going through all branches in the ILanguageBranchRepository and either enable or disable them. Just make sure that you do not disable all languages at some point as this will result in an exception.

private static void EnabledLanguages(ILanguageBranchRepository languageRepository, IEnumerable<string> languages)
{
    if (languages == null || !languages.Any()) return;

    var shouldDisable = languageRepository.ListEnabled()
                                    .Where(l => !languages.Contains(l.LanguageID, StringComparer.OrdinalIgnoreCase));

    foreach (var language in languages.Select(x => CultureInfo.GetCultureInfo(x)))
    {
        languageRepository.Enable(language);
    }

    // Disable language secondly to avoid exception thrown if no languages are enabled.
    foreach (var language in shouldDisable)
    {
        languageRepository.Disable(language.Culture);
    }
}

As a second step, we will create content for the test site. In our example, we will use the import system for creating content from an embedded .episerverdata file and this is probably the easiest way unless your required site is very small.

private static ContentReference ImportEmbeddedPackage(IDataImporter importer, string embeddedResourceName)
{
    // Load content package from embedded resources
    var resources = Assembly.GetExecutingAssembly().GetManifestResourceStream(embeddedResourceName);

    var options = new ImportOptions
    {
        AutoCloseStream = true,
        KeepIdentity = true,
        TransferType = TypeOfTransfer.Importing,
        ValidateDestination = false
    };

    importer.Import(resources, ContentReference.RootPage, options);

    // Root of Imported pages will be our start page
    return importer.Status.ImportedRoot;
}

Finally, we will take the root of the imported content structure and define a Site with this page as the start page. This is done by creating a SiteDefinition object and saving it to the ISiteDefinitionRepository.

private static void DefineSite(ISiteDefinitionRepository siteDefinitionRepository, string siteName, ContentReference startPage, string siteUrl)
{
     var existingSite = siteDefinitionRepository.Get(siteName);
     if (existingSite != null) return;

     // Define our site
     var siteDefinition = new SiteDefinition
     {
            Name = siteName,
            SiteUrl = new Uri($"http://{siteUrl}/", UriKind.Absolute),
            StartPage = startPage
     };
     siteDefinitionRepository.Save(siteDefinition);
}

Running the test

Now everything should be ready for you to run your tests. Simply request the service you want to test from the ServiceLocator and call your method.

[Fact]
public void RootPageChildren_ShouldContainTestPage()
{
     const string pageName = "TestPage";

     var repository = ServiceLocator.Current.GetInstance<IContentRepository>();
     var children = repository.GetChildren<IContent>(ContentReference.RootPage);

     Assert.Contains(children, x => x.Name == pageName);
}

Cleaning up

Once the test run has completed we simply have to call Uninitialize on our Initialization engine instance and drop our temporary database to clean up after ourselves.

public void Dispose()
{
     // Take down the CMS instance
     if (_engine.InitializationState == InitializationState.Initialized)
     {
          _engine.Uninitialize();
     }
     // Remove the temporary database
     _database.Dispose();
}

Wrap up

It doesn’t have to be hard to set up an Episerver CMS instance for you to run integration tests against. And while we have not covered the rendering layer in our test it can provide us with a simple and reasonably fast and robust set of tests to validate how our business services work together with the rest of the service stack.

Advertisements

I thought that I would mention a small improvement that was released with CMS 8 the other day. It’s a minor change in regards to comparisons of ContentReference instances, but as this is an area that is used a lot I thought that it would be worth mentioning. Read the rest of this entry »

Search engines often highlights the importance of using canonical URLs to signify the preferred URL that should be used when the same content can be reached using multiple URLs.

And while EPiServer has always supported the use of canonical URLs it is now easier than ever to setup multiple domains and ensure that there is only one canonical content URL from a search engine perspective. This includes adding canonical link elements to the HTML head of your pages and redirecting requests to your primary domain.

Read the rest of this entry »

When we took a look at what performance improvements that we could make to the EPiServer CMS platform there was one thing that stood out like a sore thumb, namely the PropertyDataCollection.

The main problem was how it’s base class, NameObjectCollectionBase, was handling retrieval of items in the collection in a multi-threaded scenario. As it happens to be, this scenario is one of the most commonly used on an EPiServer CMS site, since this is performed every time a content property is accessed. When profiling sites this often ended up as one of the code paths where most of the time was spent, in the case of some of the pages on the Alloy example site it turned out to be almost 20% of the total time spent rendering the page.

Read the rest of this entry »

For as long as I can remember, the logging framework of choice for EPiServer has been log4net, but this is about to change now! Well, I say that, in reality we are only changing the way we are using the logging framework so that it is possible to change framework going forward. The main reason for this change being that it allows us to upgrade log4net to the latest version, something that was not previously possible due to an earlier change of the log4net strong name key without causing major problems.

The new Logging API that is shipping with EPiServer.Framework since version 7.17.0 is not meant to compete or be a replacement for existing logging frameworks such as log4net, but merely a simple abstraction for writing messages to a logging framework. To manage the configuration and output of the logger, simply use the API of the implementing framework of your choice.

Read the rest of this entry »

Many of you have seen how some of the EPiServer modules are being deployed as compressed ZIP packages and have asked if you can do the same with your own modules. To cater for these requests we added a virtual path provider with support for ZIP files to the EPiServer.Framework package called ZipArchiveVirtualPathProvider. This addition was released as a part of version 7.15.0.
Read the rest of this entry »

With EPiServer 7 came the introduction of two new features; the ability to divide a page into blocks or smaller reusable components and support for the ASP.NET MVC framework in templates.

If we want to combine these two features together and create a custom form placed in an MVC block template, there are a few things that we need to solve to get a properly working solution and this is what we will explore in this post.
Read the rest of this entry »