DEV Community

Sean G. Wright
Sean G. Wright

Posted on • Originally published at Medium on

Kentico 12: Design Patterns Part 1 - Writing Testable Code

Photo by Nicolas Thomas on Unsplash

The Good: Why We Love Kentico šŸ˜»

In my opinion, Kentico has always been designed to make its users productive. This is true for both the classic Portal Engine drag-and-drop UI builder and the .NET libraries developers can use to interact with the CMS.

Do you need to get user data from the database? Call the static provider class with UserInfoProvider.GetUsers();. āœ”ļø

Do you need to know which site the current request is running under? Use the static context accessor SiteContext.CurrentSiteName;. āœ”ļø

Want to ensure some data that is accessible as type object is actually the int value you originally stored? Wrap it in the static utility method call ValidationHelper.GetInteger(originalData, 0);. āœ”ļø

The key thing to notice about all these calls is that they are static methods often on static classes.

These classes donā€™t require instantiation, we donā€™t need to understand their dependency graph, they come to us pre-packaged and ready to use, and are truly global values as long as we have access to the .dll in which they are deployed.

The implication of this design is that all of the above tools that developers have access to, for building complex web sites on the Kentico CMS, can be discovered and leveraged as simply as possible. šŸ™Œ

The Bad: Skeletons in the Closet šŸ’€

There is a dark side to this happy world of static, global access.

These types are often tightly coupled to their dependencies, some can only be used in a specific context (that of a live request going through the ASP.NET request pipeline), and can be difficultā€Šā€”ā€Šif not impossibleā€Šā€”ā€Što unit test. šŸ˜±

From here on Iā€™m going to refer to things being ā€˜testableā€™ or ā€˜un-testableā€™ by which I mean unit testing specifically.

This consequence isnā€™t exactly Kenticoā€™s fault.

ASP.NET Web Forms was not a technology that encouraged testing or a decoupled architecture. It was built to ensure optimal productivity for Windows Forms desktop developers moving to building web applications.

Kentico is built on Web Forms and therefore is going to mimic its patterns and prioritiesā€Šā€”ā€Šease of use, abstracting away the ā€˜webā€™ part of web development, and access to static "ambient context" globals like HttpContext.

If youā€™ve never heard of Ambient Context before, you can read up on the design pattern here. One item to note is under the Consequences section where it is stated ā€œThe consequences to the system of making use of an ambient context object will be dependent on the problem domainā€. Here "problem domain" is equivalent to "real running ASP.NET site processing an active HTTP request".

But now weā€™re starting a new era of Kentico development where our code is going to be run both in the Web Forms CMS architecture and the newer MVC architecture.

MVC was designed to separate concerns, decouple declarative UI from procedural logic, and allow for patterns like Inversion of Control, Dependency Injection and Composition instead of Inheritance. šŸ¤“

How can we, as developers, bridge the gap between the powerful set of libraries and tools provided by Kentico to integrate with the CMS and the best-practice architectural patterns allowed (and encouraged) by MVC? šŸ„ŗ

Not all of Kenticoā€™s classes have these issues. For example, ValidationHelper is static but also basically a pure function that attempts to convert a value of type object to a specific type. It can safely be used anywhere in your code and will give the same results at runtime that it will at test time.

But Wait, Thereā€™s Hope! šŸ¤—

Letā€™s look at a simple example of how to encapsulate the convenient but un-testable parts of our traditional Kentico applications.

For this example, we are going to look at accessing Kenticoā€™s settings API which allows for configuration, normally stored in <appSettings> in the web.config of a site to be stored in Kenticoā€™s database.

How might you normally get this data for the site of the current request? šŸ¤”Maybe the following:

string settingValue = SettingsKeyInfoProvider.GetValue("someKeyString", SiteContext.CurrentSiteName);

While Kentico does provide us a way to test SettingsKeyInfoProvider.GetValue() using Fake<> in unit tests, there is no way to unit test SiteContext.

SiteContext, along with all the other convenient static ***Context types, are un-testable. They require a real ASP.NET application with a live request in order to work correctly, and because they are static, we cannot mock them. šŸ˜’

How do we build our own API that provides access to this data but doesnā€™t create a dependency on SiteContext?

Interfaces are Here to Help

The example code below introduces our first and best tool to make our applications more testable: interfaces.

In C#, interfaces canā€™t be implemented by static classes, so that immediately saves us from ending up in the situation we already find ourselves in.

But they also provide a seam into which we can insert existing calls to Kenticoā€™s static classes and methods when needed, without exposing that implementation. No need to throw the baby out with the bathwater. We donā€™t want to re-write the CMS; we just want it to be testable. šŸ˜‰

This interface defines a way to access the settings configured for Kentico, both globally and per-site when running a multi-site instance.

Now we have an interface that we can implement however we choose, and as long as our MVC code takes a dependency on this interface (likely through Dependency Injection), we can ensure our code is testable. šŸ‘

Letā€™s Implement

Okay, so weā€™ve basically moved the goal post. Our MVC code will depend on IKenticoSettingConfigProvider, but we will need an implementation for this interface at some point.

Our first thought might be to create a class that directly uses the example SettingsKeyInfoProvider.GetValue() call above.

This certainly works, but if our KenticoSettingConfigProvider implementation class starts to have any complex logic, we are going to want to test it as well.

Like I mentioned above, while SettingsKeyInfoProvider.GetValue() is testable, SiteContext is notā€Šā€”ā€Šso our initial implementation of this class is, again, not testable. šŸ¤¦šŸ¾ā€ā™‚ļø

A Quick Aside: Whatā€™s the Value? šŸ’²

Even if the amount of logic in our implementation class is low and we donā€™t feel the need to write tests for it, thereā€™s still value in designing it with testing in mind.

To effectively test code, we need to identify dependencies and ensure those dependencies can be mocked or isolated from the code we want to test. This identification process is valuable in itself. By identifying these dependencies, we come to realize what assumptions our code makes about how and where it can be run. šŸŒŸ

What does it mean when your code takes a dependency on MemebershipContext or HttpContext?

Are these always available when your code is executing? Will they always have the values you expect? What about in a Scheduled Task or a background thread? šŸ¤”

I remember writing some logging code that accessed HttpContext to log additional request details. Only later did I find out that HttpContext doesnā€™t always exist when the logging happens. This caused my logging code to throw exceptions. I made assumptions about where and how my code was run that did not hold true.

Had HttpContext not been buried deep in a logging method, but instead was specified as a constructor dependency, I would have had a better chance of understanding my failed use-case ahead of time.

Writing for Testability šŸ‘©ā€šŸ’»

Letā€™s look at how we can make our KenticoSettingConfigProvider class testable.

We know SiteContext isnā€™t testable, so letā€™s stop using it directly and instead create a seam we can hide the implementation details behind.

Here we create an ISiteContext interface that exposes the same values we would need from SiteContext, but since itā€™s an interface, we can mock it.

If we want our KenticoSettingConfigProvider to take a dependency on the mocked version of our ISiteContext when under test, we need to be able to supply that mocked version.

The best way to do this is through constructor Dependency Injection.

You can see above how we take a dependency on ISiteContext via the constructor of KenticoSettingConfigProvider.

If you are wondering about the Guard.Against.Null() call in the constructor above, take a look at Steve Smithā€™s GuardClauses library. Itā€™s a great way to guard against invalid parameters for constructors or methods by throwing an exception if the requirements of the guard are not met.

I love how itā€™s declarative, simple, and easy to reason about. I use them in all the code I write and I write tests that ensure they are in place.

The advantage we gain here is that we can simulate the site of the current request in our tests. When we test the methods of KenticoSettingConfigProvider we will mock ISiteContext with an implementation that, for example, always returns "MySite" for the SiteName property.

If we Fake<SettingsKeyInfo, SettingsKeyInfoProvider> and Fake<SiteInfo, SiteInfoProvider> to match up with "MySite", then our ISiteContext will supply a SiteName that will match up with the settings we expect our class to return. šŸ‘

The Real Context šŸ”©šŸ”§

Okay, so weā€™ve now abstracted our MVC code from the static types in Kenticoā€™s libraries through the IKenticoSettingConfigProvider interface, and also abstracted our KenticoSettingConfigProvider away from the un-testable SiteContext through the ISiteContext interface.

Letā€™s implement ISiteContext with something that will actually work at runtime.

Well, this is pretty simple, isnā€™t it? We forward the properties of our interface to the properties of the SiteContext.

My recommendation is to use an interface like ISiteContext throughout your MVC codebases whenever you need access to that Kentico data/context goodness, and leave the real SiteContext as an implementation detail hidden behind ISiteContext. šŸ‘©šŸ¾ā€šŸ”§

This will help make your code more composable, help you identify the dependencies your code has on external data and resources, and perhaps most importantly, help make your code more testable.

You get to leverage the power of Kentico without being subject to the drawbacks of a classic ASP.NET Web Forms influenced architecture! šŸ‘šŸ½

Whatā€™s Next?

Phew šŸš“šŸ½ā€ā™€ļø. That was a bit of work, but weā€™re in a better place because of it. šŸ—ŗļø

Weā€™ve learned how to identify and isolate the un-testable pieces of Kenticoā€™s infrastructure, and written our own testable library code.

So where do we go from here? šŸ§—šŸ»

In my next post, Iā€™ll introduce some techniques and tools to help you test your Kentico code effectively while also reducing boilerplate. šŸ‘‹šŸ¾

Kentico 12: Design Patterns Part 2

Top comments (0)