Skip to main content

Unit Testing your TBBs

One of the reasons I was Messing with the Engine is testing. I wanted to write unit tests for Tridion Template Building Blocks and for that I needed the possibility to run my templates outside of the Content Manager.

Approach

The approach is to run templates from an external project (be it stand-alone application, or a testing framework of your choice) and be able to check the Package contents at different stages in the execution. Then I would simply compare some expected Package items or even the Output item to contain some expected results.

In order to run templates in Tridion CM, one would need an instance of the Engine and Package objects. The Package is quite simple, as you would simply instantiate one using Package(Engine engine) constructor. Getting an instance of Engine on the other hand was quite excruciating -- somebody in Tridion R&D put it great effort in making that class final, sealed, internal, unextendable, unwrappable, private constructors, <fill in here your preferred C# access modifier>, etc. Well, too bad for all that effort, because one way or another there is always a solution -- in my case, enter reflection... but more on that later.

Usage

I came up with my own engine implementation TestEngine and I am able to run it on a Page or Component while passing the Page Template to render with, or Component Template respectively:

TestEngine engine = new TestEngine("tcm:20-102-64", "tcm:20-707-128");
engine.Run();

In the code above, a Page is rendered with a PT. For that TestEngine creates its own Package and then, by calling Run(), it simply fires off the template rendering process. Once Run() finishes, all TBBs in the template would have executed and the Package can be inspected for getting the success/failure status of the unit test.

foreach (KeyValuePair<string, Item> pair in engine.Package.GetEntries()) {
    Item item = pair.Value;
    Console.WriteLine(string.Format("Item {0} | Type {1} | Content {2}",
            pair.Key, item.ContentType, item.GetAsString()));
}

With the current implementation, the whole template is executed, but also more fine-grained approach is possible -- where only a specified TBB would be executed.

TestEngine Implementation

But let's see the implementation of TestEngine. It is a TemplatingRenderer specialization:

public class TestEngine : TemplatingRenderer

Constructor

TemplatingRenderer contains a bunch of useful logic, which I simply wanted to re-use. But before being able to do so, the Engine has to be initialized, so the Package and RenderedItem members need to be assigned. Again, with Package there is no problem, but _renderedItem is not exposed. Here comes in the first hack -- set _renderedItem using reflection. I do this in the TestEngine constructor:

public TestEngine(string pageOrComponentTcmUri, string templateTcmUri) {
    _session = new Session();

    itemToRender = _session.GetObject(pageOrComponentTcmUri);
    template = _session.GetObject(templateTcmUri) as Template;

    typeof(TemplatingRenderer).GetField("_renderedItem"BindingFlags.Instance | BindingFlags.NonPublic)
        .SetValue(thisnew RenderedItem(
            new ResolvedItem(itemToRender, template),
            new RenderInstruction(_session) { RenderMode = RenderMode.PreviewDynamic }
        )
    );
}

Run

Now that I have an instance of the Engine, let's execute the template on a Page or Component. There is one public method Render which kicks off the entire execution, but it does too much for me -- I need something more fine-grained and where I can have access to the Package. This method is Engine.TransformPackage(Template, Package), which only deals with the rendering part of the process. The problem with it is its visibility - internal. Here comes hack #2 and again reflection comes to the rescue:

public void Run() {
    typeof(Engine).GetMethod("TransformPackage"BindingFlags.Instance | BindingFlags.NonPublic)
        .Invoke(thisnew object[] { Template, Package });
}

Notice the property Package that I'm passing to TransfrormPackage. This is basically the object I create and the one that I'm inspecting at the end of the template rendition.

All Together

Finally, putting it all together, this is the final class:

public class TestEngine : TemplatingRenderer {

    private IdentifiableObject itemToRender;
    public IdentifiableObject ItemToRender {
        get { return itemToRender; }
    }

    private Template template;
    public Template Template {
        get { return template; }
    }

    private Package package;
    public Package Package {
        get {
            if (package == null) {
                package = new Package(this);
                if (itemToRender.Id.ItemType == ItemType.Component) {
                    Item item = package.CreateTridionItem(ContentType.Component, itemToRender);
                    package.PushItem(Tridion.ContentManager.Templating.Package.ComponentName, item);
                } else {
                    Item item = package.CreateTridionItem(ContentType.Page, itemToRender);
                    package.PushItem(Tridion.ContentManager.Templating.Package.PageName, item);
                }
            }

            return package;
        }
    }

    public TestEngine(string pageOrComponentTcmUri, string templateTcmUri) {
        _session = new Session();

        itemToRender = _session.GetObject(pageOrComponentTcmUri);
        template = _session.GetObject(templateTcmUri) as Template;

        typeof(TemplatingRenderer).GetField("_renderedItem", BindingFlags.Instance | BindingFlags.NonPublic)
            .SetValue(this, new RenderedItem(
                new ResolvedItem(itemToRender, template),
                new RenderInstruction(_session) { RenderMode = RenderMode.PreviewDynamic }
            )
        );
    }

    public void Run() {
        typeof(Engine).GetMethod("TransformPackage", BindingFlags.Instance | BindingFlags.NonPublic)
            .Invoke(this, new object[] { Template, Package });
    }
}


Comments

Jeremy Simmons said…
have you done any unit testing work with the Event System? Have you mocked any other classes?
Unknown said…
Unfortunately not, but there goes a good blog post idea ;) Stay tuned...

The approach would be somewhat similar to testing TBBs:
- create a Session object;
- get Tridion item using Session;
- 'fake' a call to the Event System handler method (of course the event phases won't work, because we are not actually in event system context);
Unknown said…
For implementing the unit test, session object is used which implies that you must have CME installed on the machine where you are running the unit test. CME may not be available on all developer machine so using this unit test implementation is not possible.
As per my understanding, unit testing should be independent of machine.
Can you please suggest any other alternative for implementing unit testing for TBB and Event System?

Popular posts from this blog

Running sp_updatestats on AWS RDS database

Part of the maintenance tasks that I perform on a MSSQL Content Manager database is to run stored procedure sp_updatestats . exec sp_updatestats However, that is not supported on an AWS RDS instance. The error message below indicates that only the sa  account can perform this: Msg 15247 , Level 16 , State 1 , Procedure sp_updatestats, Line 15 [Batch Start Line 0 ] User does not have permission to perform this action. Instead there are several posts that suggest using UPDATE STATISTICS instead: https://dba.stackexchange.com/questions/145982/sp-updatestats-vs-update-statistics I stumbled upon the following post from 2008 (!!!), https://social.msdn.microsoft.com/Forums/sqlserver/en-US/186e3db0-fe37-4c31-b017-8e7c24d19697/spupdatestats-fails-to-run-with-permission-error-under-dbopriveleged-user , which describes a way to wrap the call to sp_updatestats and execute it under a different user: create procedure dbo.sp_updstats with execute as 'dbo' as

Content Delivery Monitoring in AWS with CloudWatch

This post describes a way of monitoring a Tridion 9 combined Deployer by sending the health checks into a custom metric in CloudWatch in AWS. The same approach can also be used for other Content Delivery services. Once the metric is available in CloudWatch, we can create alarms in case the service errors out or becomes unresponsive. The overall architecture is as follows: Content Delivery service sends heartbeat (or exposes HTTP endpoint) for monitoring Monitoring Agent checks heartbeat (or HTTP health check) regularly and stores health state AWS lambda function: runs regularly reads the health state from Monitoring Agent pushes custom metrics into CloudWatch I am running the Deployer ( installation docs ) and Monitoring Agent ( installation docs ) on a t2.medium EC2 instance running CentOS on which I also installed the Systems Manager Agent (SSM Agent) ( installation docs ). In my case I have a combined Deployer that I want to monitor. This consists of an Endpoint and a

Debugging a Tridion 2011 Event System

OK, so you wrote your Tridion Event System. Now it's time to debug it. I know this is a hypothetical situtation -- your code never needs any kind of debugging ;) but indulge me... Recently, Alvin Reyes ( @nivlong ) blogged about being difficult to know how exactly to debug a Tridion Event System. More exactly, the question was " What process do I attach to for debugging even system code? ". Unfortunately, there is no simple or generic answer for it. Different events are fired by different Tridion CM modules. These modules run as different programs (or services) or run inside other programs (e.g. IIS). This means that you will need to monitor (or debug) different processes, based on which events your code handles. So the usual suspects are: dllhost.exe (or dllhost3g.exe ) - running as the MTSUser is the SDL Tridion Content Manager COM+ application and it fires events on generic TOM objects (e.g. events based on Tridion.ContentManager.Extensibility.Events.CrudEven