Null Object Design Pattern in Automated Testing

Null Object Design Pattern in Automated Testing

If you are a regular reader of Automate The Planet you have most probably read some of my articles about Design Patterns in Automated Testing. The newest article from the famous series is dedicated to the Null Object Design Pattern. I am going to explain how to use the pattern to create a default behavior for your strategies and achieve a cleaner and more concise tests’ code. Less branching in the code means lower complexity.

Definition

In object-oriented computer programming, a Null Object is an object with no referenced value or with defined neutral (“null”) behavior. The Null Object Design Pattern describes the uses of such objects and their behavior (or lack thereof).

Benefits

  • Rid program logic of null checks where possible

  • Provide a non-functional object in place of a null reference

  • Allow methods to be called on Null objects, unlike a null reference

UML Class Diagram

classDiagram
    IPurchasePromotionalCodeStrategy <|.. UiPurchasePromotionalCodeStrategy
    IPurchasePromotionalCodeStrategy <|.. NullPurchasePromotionalCodeStrategy
    class IPurchasePromotionalCodeStrategy {
        <<interface>>
        +AssertPromotionalCodeDiscount()
        +GetPromotionalCodeDiscountAmount()
        +ApplyPromotionalCode(string couponCode)
    }
    class UiPurchasePromotionalCodeStrategy {
        +AssertPromotionalCodeDiscount()
        +GetPromotionalCodeDiscountAmount()
        +ApplyPromotionalCode(string couponCode)
    }
    class NullPurchasePromotionalCodeStrategy {
        +AssertPromotionalCodeDiscount()
        +GetPromotionalCodeDiscountAmount()
        +ApplyPromotionalCode(string couponCode)
    }

The classes and objects participating in Null Object Design Pattern are:

  • IPurchasePromotionalCodeStrategy

    Defines the interface for all strategies.

  • UiPurchasePromotionalCodeStrategy

    The strategy that is responsible for applying and asserting promotional codes through the UI of the application.

  • NullPurchasePromotionalCodeStrategy

    The null implementation of the strategy interface, provides the default implementation when no promotional code is applied.

Null Object Design Pattern C# Code

Test’s Test Case

As in most of the examples from the series, we are going to automate a shopping cart process, the Online Store’s one in particular. I am not going to explain the whole process, you can check the full explanations in my articles dedicated to the Strategy Design Pattern. The page that is interesting for us is the last page from the purchase process- Place Order Page. There you can apply promotional codes and gift cards.

Online Store Place Order Page

The main idea is that sometimes we need to add promotional codes and then assert that the correct amounts are displayed or saved in the DB. As you can assume there are various ways to accomplish that. One way is to use the UI directly and assert the text present in the labels. Another way is to use a direct access to the DB and insert the promotional code, then assert the calculated entries saved in some of the DB’s tables. Also, you can achieve the same thing using a web service. I think you get the point- there are multiple solutions to the problem. In order to be able to understand fully the explanations below, I assume that you have read about the Strategy Design Pattern. If you haven’t, I suggest you to do so. I think that one of the best solutions for the previously stated problem is to use the strategy design pattern. 

classDiagram
    PurchaseContext --> IPurchasePromotionalCodeStrategy
    IPurchasePromotionalCodeStrategy <|.. UiPurchasePromotionalCodeStrategy
    IPurchasePromotionalCodeStrategy <|.. NullPurchasePromotionalCodeStrategy
    class IPurchasePromotionalCodeStrategy {
        <<interface>>
        +AssertPromotionalCodeDiscount()
        +GetPromotionalCodeDiscountAmount()
        +ApplyPromotionalCode(string couponCode)
    }
    class PurchaseContext {
        -IPurchasePromotionalCodeStrategy strategy
        +PurchaseItem(string itemUrl, string itemPrice)
    }
    class UiPurchasePromotionalCodeStrategy {
        +AssertPromotionalCodeDiscount()
        +GetPromotionalCodeDiscountAmount()
        +ApplyPromotionalCode(string couponCode)
    }
    class NullPurchasePromotionalCodeStrategy {
        +AssertPromotionalCodeDiscount()
        +GetPromotionalCodeDiscountAmount()
        +ApplyPromotionalCode(string couponCode)
    }

The context holds a dependency to IStrategy and wraps the calls to the concrete strategies. In our case, the purchasing workflow is placed in the PurchaseContext class. We call the PurchaseItem method to perform a purchase. The PurchaseContext has a dependency to IPurchasePromotionalCodeStrategy. So depending on what we want to test we can  use the UI to apply and assert the promotional codes or use a direct DB access for a faster tests’ execution.

IPurchasePromotionalCodeStrategy Interface

public interface IPurchasePromotionalCodeStrategy
{
    void AssertPromotionalCodeDiscount();
    double GetPromotionalCodeDiscountAmount();
    void ApplyPromotionalCode(string couponCode);
}

The interface contains only three methods. One that applies the code, one to assert the code and the last one to get the discount’s amount.

PurchaseContext Implementation without Null Object Design Pattern

This is how looks the PurchaseContext code if we don’t use the Null Object Design Pattern.

public class PurchaseContextNoNullObjects
{
    private readonly IPurchasePromotionalCodeStrategy purchasePromotionalCodeStrategy;
    private readonly ItemPage itemPage;
    private readonly PreviewShoppingCartPage previewShoppingCartPage;
    private readonly SignInPage signInPage;
    private readonly ShippingAddressPage shippingAddressPage;
    private readonly ShippingPaymentPage shippingPaymentPage;
    private readonly PlaceOrderPage placeOrderPage;
    public PurchaseContextNoNullObjects(
    IPurchasePromotionalCodeStrategy purchasePromotionalCodeStrategy,
    ItemPage itemPage,
    PreviewShoppingCartPage previewShoppingCartPage,
    SignInPage signInPage,
    ShippingAddressPage shippingAddressPage,
    ShippingPaymentPage shippingPaymentPage,
    PlaceOrderPage placeOrderPage)
    {
        this.purchasePromotionalCodeStrategy = purchasePromotionalCodeStrategy;
        this.itemPage = itemPage;
        this.previewShoppingCartPage = previewShoppingCartPage;
        this.signInPage = signInPage;
        this.shippingAddressPage = shippingAddressPage;
        this.shippingPaymentPage = shippingPaymentPage;
        this.placeOrderPage = placeOrderPage;
    }
    public void PurchaseItem(
    string itemUrl,
    string itemPrice,
    ClientLoginInfo clientLoginInfo,
    ClientPurchaseInfo clientPurchaseInfo)
    {
        this.itemPage.Navigate(itemUrl);
        this.itemPage.ClickBuyNowButton();
        this.previewShoppingCartPage.ClickProceedToCheckoutButton();
        this.signInPage.Login(clientLoginInfo.Email, clientLoginInfo.Password);
        this.shippingAddressPage.FillShippingInfo(clientPurchaseInfo);
        this.shippingAddressPage.ClickDifferentBillingCheckBox(clientPurchaseInfo);
        this.shippingAddressPage.ClickContinueButton();
        this.shippingPaymentPage.ClickBottomContinueButton();
        this.shippingAddressPage.FillBillingInfo(clientPurchaseInfo);
        this.shippingAddressPage.ClickContinueButton();
        this.shippingPaymentPage.ClickTopContinueButton();
        double couponDiscount = 0;
        if (purchasePromotionalCodeStrategy != null)
        {
            this.purchasePromotionalCodeStrategy.AssertPromotionalCodeDiscount();
            couponDiscount =
            this.purchasePromotionalCodeStrategy.GetPromotionalCodeDiscountAmount();
        }
        double totalPrice = double.Parse(itemPrice);
        this.placeOrderPage.AssertOrderTotalPrice(totalPrice, couponDiscount);
        // Some other actions...
        if (purchasePromotionalCodeStrategy != null)
        {
            this.purchasePromotionalCodeStrategy.AssertPromotionalCodeDiscount();
        }
    }
}

As I already have mentioned in the body of the PurchaseItem method you can find the order’s completion workflow. Though a constructor injection, we pass all dependencies of the PurchaseContext such as all required pages and the concrete implementation of the IPurchasePromotionalCodeStrategy. However, there might be cases where we don’t need to apply promotional codes and the strategy might no be initialized because of that we use null checks. If we don’t use them a NullReferenceException might be thrown.

For more detailed overview and usage of many more design patterns and best practices in automated testing, check my book “Design Patterns for High-Quality Automated Tests, C# Edition, High-Quality Tests Attributes, and Best Practices”.  You can read part of three of the chapters:

Defining High-Quality Test Attributes for Automated Tests

Benchmarking for Assessing Automated Test Components Performance

Generic Repository Design Pattern- Test Data Preparation

UI Implementation of IPurchasePromotionalCodeStrategy

This is how the UI implementation of the IPurchasePromotionalCodeStrategy interface looks.

public class UiPurchasePromotionalCodeStrategy : IPurchasePromotionalCodeStrategy
{
    private readonly PlaceOrderPage placeOrderPage;
    private readonly double couponDiscountAmount;
    public UiPurchasePromotionalCodeStrategy(
    PlaceOrderPage placeOrderPage,
    double couponDiscountAmount)
    {
        this.placeOrderPage = placeOrderPage;
        this.couponDiscountAmount = couponDiscountAmount;
    }
    public void AssertPromotionalCodeDiscount()
    {
        Assert.AreEqual(
        this.couponDiscountAmount.ToString(),
        this.placeOrderPage.PromotionalDiscountPrice.Text);
    }
    public double GetPromotionalCodeDiscountAmount()
    {
        return this.couponDiscountAmount;
    }
    public void ApplyPromotionalCode(string couponCode)
    {
        this.placeOrderPage.PromotionalCode.SendKeys(couponCode);
    }
}

This concrete implementation of IPurchasePromotionalCodeStrategy is dependent to the PlaceOrderPage. We use the UI to apply and assert the promotional codes.

Null Object Implementation of IPurchasePromotionalCodeStrategy

The default implementation of the promotional codes’ interface is pretty simple.

public class NullPurchasePromotionalCodeStrategy : IPurchasePromotionalCodeStrategy
{
    public void AssertPromotionalCodeDiscount()
    {
    }
    public double GetPromotionalCodeDiscountAmount()
    {
        return 0;
    }
    public void ApplyPromotionalCode(string couponCode)
    {
    }
}

We return zero for the discount amount and the body of the rest of the methods is empty.

PurchaseContext Implementation with Null Object Design Pattern

public class PurchaseContext
{
    private readonly IPurchasePromotionalCodeStrategy purchasePromotionalCodeStrategy;
    private readonly ItemPage itemPage;
    private readonly PreviewShoppingCartPage previewShoppingCartPage;
    private readonly SignInPage signInPage;
    private readonly ShippingAddressPage shippingAddressPage;
    private readonly ShippingPaymentPage shippingPaymentPage;
    private readonly PlaceOrderPage placeOrderPage;
    public PurchaseContext(
    IPurchasePromotionalCodeStrategy purchasePromotionalCodeStrategy,
    ItemPage itemPage,
    PreviewShoppingCartPage previewShoppingCartPage,
    SignInPage signInPage,
    ShippingAddressPage shippingAddressPage,
    ShippingPaymentPage shippingPaymentPage,
    PlaceOrderPage placeOrderPage)
    {
        this.purchasePromotionalCodeStrategy = purchasePromotionalCodeStrategy;
        this.itemPage = itemPage;
        this.previewShoppingCartPage = previewShoppingCartPage;
        this.signInPage = signInPage;
        this.shippingAddressPage = shippingAddressPage;
        this.shippingPaymentPage = shippingPaymentPage;
        this.placeOrderPage = placeOrderPage;
    }
    public void PurchaseItem(
    string itemUrl,
    string itemPrice,
    ClientLoginInfo clientLoginInfo,
    ClientPurchaseInfo clientPurchaseInfo)
    {
        this.itemPage.Navigate(itemUrl);
        this.itemPage.ClickBuyNowButton();
        this.previewShoppingCartPage.ClickProceedToCheckoutButton();
        this.signInPage.Login(clientLoginInfo.Email, clientLoginInfo.Password);
        this.shippingAddressPage.FillShippingInfo(clientPurchaseInfo);
        this.shippingAddressPage.ClickDifferentBillingCheckBox(clientPurchaseInfo);
        this.shippingAddressPage.ClickContinueButton();
        this.shippingPaymentPage.ClickBottomContinueButton();
        this.shippingAddressPage.FillBillingInfo(clientPurchaseInfo);
        this.shippingAddressPage.ClickContinueButton();
        this.shippingPaymentPage.ClickTopContinueButton();
        this.purchasePromotionalCodeStrategy.AssertPromotionalCodeDiscount();
        var couponDiscount =
        this.purchasePromotionalCodeStrategy.GetPromotionalCodeDiscountAmount();
        double totalPrice = double.Parse(itemPrice);
        this.placeOrderPage.AssertOrderTotalPrice(totalPrice, couponDiscount);
        this.purchasePromotionalCodeStrategy.AssertPromotionalCodeDiscount();
    }
}

As you have most probably noticed the code is almost identical to the previous implementation with the small difference that the null checks are missing.

Null Object Design Pattern is about giving a default implementation for filling the absence of an object and it is not about avoiding null reference exceptions. If you see Null Object Design Pattern implemented with no object collaboration then there is something wrong in the way the pattern is implemented.

Null Object Design Pattern in Tests


public class Online StorePurchaseNullObjectTests
{

public void SetupTest()
{
    Driver.StartBrowser();
}

public void TeardownTest()
{
    Driver.StopBrowser();
}

public void Purchase_SeleniumTestingToolsCookbook()
{
    string itemUrl = "/Selenium-Testing-Cookbook-Gundecha-Unmesh/dp/1849515743";
    string itemPrice = "40.49";
    ClientPurchaseInfo clientPurchaseInfo = new ClientPurchaseInfo(
    new ClientAddressInfo()
    {
        FullName = "John Smith",
        Country = "United States",
        Address1 = "950 Avenue of the Americas",
        State = "New York",
        City = "New York City",
        Zip = "10001-2121",
        Phone = "00164644885569"
    });
    clientPurchaseInfo.CouponCode = "99PERDIS";
    ClientLoginInfo clientLoginInfo = new ClientLoginInfo()
    {
        Email = "g3984159@trbvm.com",
        Password = "ASDFG_12345"
    };
    var purchaseContext = new PurchaseContext(
    new NullPurchasePromotionalCodeStrategy(),
    new ItemPage(Driver.Browser),
    new PreviewShoppingCartPage(Driver.Browser),
    new SignInPage(Driver.Browser),
    new ShippingAddressPage(Driver.Browser),
    new ShippingPaymentPage(Driver.Browser),
    new PlaceOrderPage(Driver.Browser));
    ////var purchaseContext = new PurchaseContext(
    ////new UiPurchasePromotionalCodeStrategy(
    ////new PlaceOrderPage(Driver.Browser), 20),
    //// new ItemPage(Driver.Browser),
    //// new PreviewShoppingCartPage(Driver.Browser),
    //// new SignInPage(Driver.Browser),
    //// new ShippingAddressPage(Driver.Browser),
    //// new ShippingPaymentPage(Driver.Browser),
    //// new PlaceOrderPage(Driver.Browser));
    purchaseContext.PurchaseItem(
    itemUrl,
    itemPrice,
    clientLoginInfo,
    clientPurchaseInfo);
}
}

The usage of the promotional code’s strategies in tests is straightforward. If you want to apply a promotional code, you should use the UiPurchasePromotionalCodeStrategy or if you don’t NullPurchasePromotionalCodeStrategy (where the default do-nothing behavior is wrapped). 

In the next part of the series I am going to show you how to utilize the Null Object Design Pattern to the maximum extend through the Singleton Design Pattern and Unity IOC Container.

Related Articles

Design Patterns

Singleton Design Pattern in Automated Testing

Ensure a class has only one instance and provide a global point of access to it.Instance control– prevents other objects from instantiating their own copies of

Singleton Design Pattern in Automated Testing

Design Patterns, Web Automation Java

Mastering Parameterized Tests in JUnit with Selenium WebDriver

In the evolving landscape of software testing, efficiency and coverage are paramount. JUnit 5 introduces enhanced parameterized testing capabilities, allowing d

Mastering Parameterized Tests in JUnit with Selenium WebDriver

Design Patterns

Fluent Page Object Pattern in Automated Testing

In my previous articles from the series "Design Patterns in Automated Testing", I explained in details how to improve your test automation framework through the

Fluent Page Object Pattern in Automated Testing

Design Patterns

Advanced Null Object Design Pattern in Automated Testing

This is the second article dedicated to the Null Object Design Pattern part of the Design Patterns in Automated Testing series. In the last post, I showed you h

Advanced Null Object Design Pattern in Automated Testing

Design Patterns

Page Objects- Partial Classes String Properties- WebDriver C#

Editorial Note: I originally wrote this post for the Test Huddle Blog. You can check out the original here, at their site.

Page Objects- Partial Classes String Properties- WebDriver C#

Design Patterns

Page Objects- Elements Access Styles- WebDriver C#

Editorial Note: I originally wrote this post for the Test Huddle Blog. You can check out the original here, at their site.

Page Objects- Elements Access Styles- WebDriver C#
Anton Angelov

About the author

Anton Angelov is Managing Director, Co-Founder, and Chief Test Automation Architect at Automate The Planet — a boutique consulting firm specializing in AI-augmented test automation strategy, implementation, and enablement. He is the creator of BELLATRIX, a cross-platform framework for web, mobile, desktop, and API testing, and the author of 8 bestselling books on test automation. A speaker at 60+ international conferences and researcher in AI-driven testing and LLM-based automation, he has been recognized as QA of the Decade and Webit Changemaker 2025.