DEV Community

Sean G. Wright
Sean G. Wright

Posted on • Originally published at Medium on

Kentico 12: Design Patterns Part 2 - Writing Unit Tests

Photo by José Alejandro Cuffia on Unsplash

A Quick Review

In Part 1 of this series, I detailed why we should be careful taking dependencies on static globals, like SiteContext in our Kentico code.

To summarize, these static globals provide us access to the state of our application, but they make assumptions about how and where our application is running.

A live ASP.NET Kentico 12 MVC application is a very different thing from a running unit test project, and SiteContext will provide values that you as the developer have no control over when it is being accessed within a unit test.

The goal of a unit test is to arrange a scenario by simulating an application state, act on that scenario, and then assert on the results of that action. If we do not have the ability to arrange that initial state, as is the case with SiteContext, we cannot effectively test the units of code that depend on it.

By taking a dependency on an interface instead of the static global, like ISiteContext, we can use the real SiteContext as the implementation of that interface at runtime in our application but use a stub at test time.

If we look at the KenticoSettingConfigProvider we created in Part 1, we can see we have all the pieces in place to write our tests.

Now that we’ve quickly reviewed our previous work, let’s write some tests! 🤓

Writing Our First Test

We create our test class using Kentico’s UnitTests base class, which is required to be able to interact with *Info and *InfoProvider classes without needing to have access to a real database.

We can see here that I’ve created a new test class KenticoSettingConfigProviderTests.

I’ve told NUnit that it should discover and run the tests in this class by adding the [TestFixture] attribute to my class and the [Test] attribute to my test method.

In this first test I am asserting that if I do not provide a valid parameter to my constructor, then it will throw an exception.

The .Should() assertion syntax comes from the FluentAssertions library, which is my preferred tool for writing readable assertions.

So far this is pretty standard unit testing. Let’s move on to testing our Kentico specific code.

Unit Testing Patterns and Conventions

We want to test the various methods of our KenticoSettingConfigProvider.

It uses Kentico’s SettingKeyInfoProvider to access settings from the database and our interface ISiteContext, which is an abstraction over SiteContext, when we want a site specific setting instead of a global one.

Here is an example of testing the GetString method to get a site-specific setting value:

There’s a lot going on here that we haven’t discussed yet 🤯, so let’s unpack it.

First, I’ve added a new attribute, [AutoDomainData], to the test method. [AutoDomainData] uses the AutoFixture library to create randomly generated values, for all primitive types, that I can use to set up the state of my test. Any parameter I specify in my test method signature will be populated with a non-null, generated value.

The implementation of this attribute is pretty simple:

🙋You might be wondering, “If it’s so important to control the state of our test, why do we use a library to generate random values to test against?”

That’s a great question!⚡

Some state in a unit test is very specific and needs to have explicitly defined values to ensure the test operates how we want. But other state in our test flows through the entire test without being modified, or if it is modified it is changed in a predictable way, independent of the exact value.

It’s more important that the state is consistent than having a specific set of values.

In the test case above, I’m not actually concerned with the value of the setting I’m trying to retrieve. I’m only attempting to verify it’s the value that SettingKeyInfoProvider will return for the given setting key.

I’m also not concerned with the exact value of the setting key — the only requirement is that it is directly associated with the value I’m expecting to be returned!😮

I could change this test so that each part of the test state has an explicit value as seen below:

The issue I have with this approach is that now another developer reading the test might wonder why I picked those specific values. Are they important? Do all setting keys need to be suffixed with a numeral? If I use a siteId other than 3 in a later test, is that significant? 🤔

In order to keep the focus of the test on the “Subject Under Test” (sut in the examples above), I want to remove any distractions. I consider the values of the state I’m setting up to be distractions in this case. 🧐

While the values of the state might not matter, the state itself has to be internally consistent. If I pass keyName to my .GetString() method but I use a different value when mocking my SettingKeyInfoProvider data, my test will fail.

This is an example of how the state I’m simulating flows through the test, and while the exact state values might not matter, all pieces of my test must use the provided state in the correct way. The state must be consistent.

Faking Data When Testing with Kentico

Let’s now take a look at some of the utilities I’m using to ensure my test state is correctly initialized.

The SettingInfoProviderFixture is a reusable helper class that can ensure the fake data that SettingInfoProvider and SiteInfoProvider access is initialized.

I’m using the Kentico AutomatedTestsWithData abstract class as a base class for my fixture. This gives me access to the Fake<,>(), method which is used to initialize the state of data the *InfoProvider classes will access. 👩‍💻

Normally you would have these calls to Fake<,>() in your test methods, but this setup code is pretty noisy and distracts from the “Subject Under Test”.

The UnitTests class, from which all Kentico unit test classes must inherit, itself inherits from AutomatedTestsWithData so this is a convenient way to move some of that arranging of state out of the test method.

Stubbing Our Context

If we look back at the way we previously defined ISiteContext, we can see it provides read-only access to a couple of values — Site, SiteId, and SiteName.

The ISiteContext that is defined as a method parameter in the unit test method is created by AutoFixture with the help of a mocking library NSubstitute.

This ISiteContext is not null but it also doesn’t have any actual behavior — that is up to us to define.

Above, we can see the call siteContext.SiteName.Returns(), which is using the NSubstitute .Returns() extension method. It allows us to explicitly define what value will be returned by this stub during the execution of our test.

We know that KenticoSettingConfigProvider, internally, uses siteContext.SiteName to query the SettingInfoProvider data for a setting value that matches the given setting key and site:

Here again you can see how the randomly generated state flows through our test in a consistent way to ensure that the state we act against is arranged in the same was as a real Kentico site. 👍🏽

Once we have all of our state arranged for our test, we can act on that state by calling .GetString() on our KenticoSettingConfigProvider.

Finally, we get a response from the method call, and we can assert that the value returned should match the value we arranged in our initial state configuration.

Executing our unit test shows that the test passes!🌟💥🤸🏾‍♀️

What We Accomplished

Looking back we can see how several decisions we made in designing our test came together to help us ensure our test would clearly show what was being tested, arrange our state consistently, and also pass despite not running in a live environment.

  • Integrating AutoFixture with NUnit allowed us to push the focus of our test away from the exact state we were simulating and onto the behavior of the “Subject Under Test”.✔️
  • Moving the setup of Kentico *InfoProvider data into a separate fixture class gave us a reusable tool for writing tests and also reduced the state arrangement distractions in our test method.✔️
  • Since we relied on ISiteContext instead of the static global SiteContext we were able to ensure that our arranged state was not only flowed through our method call and *InfoProvider data stores, but also into the simulated request context that would, in a real running application, reflect the correct Kentico site the current request was associated with.✔️

I hope you found this review, of what I believe to be effective unit testing patterns when working with Kentico, helpful. 👍

Next in this blog series, I’m going to cover some organizational patterns in Kentico 12 MVC projects that can help you scale your applications and manage complexity using thoughtful conventions. 👨‍🏫

Have you read my previous post in this series “Kentico 12: Design patterns Part 1 — Writing Testable Code”?

Kentico 12: Design Patterns Part 1

Latest comments (0)