Kentico 12 - Design Patterns (21 Part Series)
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! 🤓
We create our test class using Kentico’s
UnitTests base class, which is required to be able to interact with
*InfoProvider classes without needing to have access to a real database.
We can see here that I’ve created a new test class
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.
.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.
We want to test the various methods of our
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.
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
.GetString()method but I use a different value when mocking my
SettingKeyInfoProviderdata, 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.
Let’s now take a look at some of the utilities I’m using to ensure my test state is correctly initialized.
SettingInfoProviderFixture is a reusable helper class that can ensure the fake data that
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”.
UnitTestsclass, from which all Kentico unit test classes must inherit, itself inherits from
AutomatedTestsWithDataso this is a convenient way to move some of that arranging of state out of the test method.
If we look back at the way we previously defined
ISiteContext, we can see it provides read-only access to a couple of values —
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.
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
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!🌟💥🤸🏾♀️
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
*InfoProviderdata 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
ISiteContextinstead of the static global
SiteContextwe were able to ensure that our arranged state was not only flowed through our method call and
*InfoProviderdata 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”?