DEV Community

Cover image for Kentico CMS Quick Tip: Faking A Live Site For Integration Testing
Sean G. Wright for WiredViews

Posted on

Kentico CMS Quick Tip: Faking A Live Site For Integration Testing

Photo by Mihรกly Kรถles on Unsplash

Overview

  • Integration tests give confidence in complex code interactions
  • Some of Kentico's APIs are difficult to test, because they depend on a live ASP.NET application context
  • We can fake this context if we know which parts are accessed during our test
  • Testing Kentico's ShoppingService lets us explore how the E-Commerce functionality works and verify the results of our custom code

Kentico Integration Tests

Integration tests in Kentico projects are perfect for simulating interactions between classes that depend on external resources, namely the database.

Many interactions with Kentico's APIs can be performed in unit tests, but some (like UserInfoProvider.IsUserInRole) require a real database connection ๐Ÿค”.

Other times, our code performs a complex series of steps, working with Kentico's types in specific ways defined by our business requirements.

Unit tests might not be enough to guarantee that these interactions will operate how we expect at runtime, and integration tests might be more appropriate.

You can read an example of what we might want in an integration test in my post Kentico CMS Quick Tip: Integration Testing Roles

However, there are some APIs that don't even work ๐Ÿ˜ฏ in an integration test scenario because they depend on a live running ASP.NET application, interacting with things like HttpContext and Session.

Fortunately there's a solution to this predicament ๐Ÿ˜…!

Let's look at an example.


Testing ShoppingService

Kentico provides many service types behind interfaces that are registered with its own Service Locator, Service, found in the CMS.Core namespace.

If we are using Dependency Injection, we could always mock these interfaces in our unit tests, but that doesn't fulfill the requirement of getting validation that the runtime interactions of multiple classes (and the database) will work as we expect.

Below we will look at running an integration test that works with ShoppingService ๐Ÿ’ฐ.

Creating The Test Class

First, we create a standard Kentico integration test, applying the correct NUnit test attributes and creating a test method:

[TestFixture]
public class ShoppingServiceTests : IntegrationTests
{
    [Test]
    public void ShoppingService_AddItemsToCart_Will_Update_CartItems()
    {
        // ...
    }
}
Enter fullscreen mode Exit fullscreen mode

You can read more about setting up integration tests in Kentico in my post Kentico 12: Design Patterns Part 8 - Setting Up Integration Tests



Our Initial Attempt

In our test method, we are going to work with CMS.Ecommerce.ShoppingService (through the IShoppingService interface), calling AddItemToCart(), which we would expect to update the database, inserting a row in COM_ShoppingCartSKU (for a new shopping cart).

Below we can see how we retrieve an ShoppingService instance from the Service service locator class and then immediately call AddItemToCart().

int skuId = 118;
int quantity = 1;

var shoppingService = Service.Resolve<IShoppingService>();

shoppingService.AddItemToCart(skuId, quantity);

var cart = shoppingService.GetCurrentShoppingCart();

cart.CartItems.Count.Should().Be(1);
Enter fullscreen mode Exit fullscreen mode

This test will fail because this method requires that the execution context is a live running ASP.NET application, not a test application ๐Ÿ˜ข.

The AddItemToCart() method assumes there is a user associated with the current request, whether that is an anonymous user or an authenticated one.

It also assumes there is a site that the request is being processed under. That site must have a valid Kentico license to allow for the E-Commerce functionality to be accessed ๐Ÿ˜ต.

In our test method there is no "current request", so trying to access it for a user (by checking the HttpContext.User.Identity), or a site (by checking HttpContext.Request.Url.Host) will fail.

The above code will throw an exception at the line shoppingService.AddItemToCart(skuId, quantity); due to a foreign key constraint caused by the shopping cart not being persisted to the database correctly ๐Ÿคฆ๐Ÿฟโ€โ™€๏ธ.


Faking a Live Site

So, how can we "fake" a live ASP.NET application? By faking the parts that get used by the ShoppingService.

First, we assign a valid instance of HttpContext to HttpContext.Current, only filling in the values needed by the ShoppingService.AddItemToCart():

string siteUri = "https://localhost:44397";
string page = "/";

HttpContext.Current = new HttpContext(
    new HttpRequest(page, siteUri , ""), 
    new HttpResponse(null))
    {
        User = new ClaimsPrincipal(new ClaimsIdentity())
    };
Enter fullscreen mode Exit fullscreen mode

The siteUri that is listed above has to be a domain that has a valid license in the CMS (in this case, localhost) ๐Ÿ‘.

We also fill in the HttpContext.User, not with anything valid, but just ensuring nothing is null (this is due to the ShoppingService requiring this value to not be null internally).

Next, we use Kentico's VirtualContext static class to assign some values that would normally be populated by the current HTTP request.

Most of the *Context static classes source some data from VirtualContext, so setting values here will ensure they flow to the rest of Kentico's classes ๐Ÿคฏ:

string siteCodeName = "Sandbox";
string currentUserName = "sean@wiredviews.com";

VirtualContext.SetItem(VirtualContext.PARAM_SITENAME, siteCodeName);
VirtualContext.SetItem(VirtualContext.PARAM_USERNAME, currentUserName);
Enter fullscreen mode Exit fullscreen mode

I assign a real username under the VirtualContext.PARAM_USERNAME key, so that when data is pulled from the database for the "current user", real information comes back. This user is also a valid Customer within the system.

I set the site name to match the site with a domain alias matching the siteUri I used above ๐Ÿง.

Now, we can write some code to validate our faked data is being passed around Kentico's libraries and giving us the state we desire.

Specifically, there should be an authenticated user in the system, and that user should be the same one I set with the VirtualContext above ๐Ÿ™‚:

bool isAuthenticated = AuthenticationHelper.IsAuthenticated();

isAuthenticated.Should().BeTrue();

var currentUser = MembershipContext.AuthenticatedUser;

currentUser.UserName.Should().Be(currentUserName);
Enter fullscreen mode Exit fullscreen mode

Now that we have our simulated live ASP.NET context within our test method we can start making calls to ShoppingService.AddItemToCart():

int skuId1 = 118;
int skuId2 = 200;
int quantity = 1;

var shoppingService = Service.Resolve<IShoppingService>();

shoppingService.AddItemToCart(skuId1, quantity);

var cart = shoppingService.GetCurrentShoppingCart();

cart.CartItems.Count.Should().Be(1);

shoppingService.AddItemToCart(skuId2, quantity);

cart = shoppingService.GetCurrentShoppingCart();

cart.CartItems.Count.Should().Be(2);
Enter fullscreen mode Exit fullscreen mode

Above, we add an item to the cart, ensure the CartItems count is 1, and then add a different item, asserting the CartItems count is 2.

If we were to debug this code, we'd see that the value returned by shoppingService.GetCurrentShoppingCart() would be a cart assigned to the site and Customer / User matching the username we populated in the VirtualContext ๐Ÿคฏ.

This also means that if we skip deleting the cart when the test ends, and run it again, there will already be items in the cart from the previous test run - all of this data is coming from the database ๐Ÿค“.

Pretty cool โšกโšก๐Ÿ‘!


Pros and Cons

Faking a live environment can definitely be helpful, but its not easy. We have to know what dependencies the Kentico classes have - like VirtualContext and HttpContext - to ensure we fake the correct data.

Using a decompiler, or viewing the Kentico source code (if you have access to it), is really the only option here ๐Ÿคท๐Ÿฝโ€โ™€๏ธ.

That said, it allows us to automate the interaction of many complex parts of our application - potentially scripting out an entire E-Commerce process from "Add To Cart", to "Payment", and finally to "Create Order" ๐Ÿ˜ฎ.

If you've tried to test these processes before by stepping through the checkout process on the live site, manually clicking things until something succeeds or fails, an integration test like the one above might be a nice alternative ๐Ÿค—.

It also can be helpful to see exactly how different combinations of Kentico's libraries interact together and what all the different APIs really do.

An integration test is like a mini-application where we can explore ideas, but without having to pay the cost of waiting for a site to start-up with every change we make ๐Ÿ˜Š.


Wrap Up

In this post we started out explaining the benefits of integration tests compared to unit tests, but realized that even integration tests can have issues when the code being tested needs to be run in an environment different than what the test simulates.

Some Kentico APIs expect a live ASP.NET request context to function correctly, which means if we want to test these we need to simulate them in our test.

Using HttpContext.Current and VirtualContext, we can simulate what Kentico's ShoppingService does when a real customer adds an item to their shopping cart ๐Ÿ’ช.

Figuring out these hidden dependencies can be difficult, but the reward is an integration test that both helps us better understand Kentico and verifies the behavior of our custom code that uses Kentico's APIs.

In the future I'd like to expand this ShoppingService integration test to include discounts, coupons, and creating an order from a shopping cart, so follow me to be alerted when I publish here on DEV ๐Ÿค“.

Thanks for reading ๐Ÿ™!


If you are looking for additional Kentico content, checkout the Kentico tag here on DEV:

#kentico

Or my Kentico blog series:

Top comments (0)