Click here to Skip to main content
15,882,017 members
Articles / Programming Languages / C#

Advanced Null Object Design Pattern in Automated Testing

Rate me:
Please Sign up or sign in to vote.
5.00/5 (2 votes)
23 Apr 2016Ms-PL4 min read 9K   2  
Improve your tests even further using the Null Object Design Pattern. Enhance your null objects through the usage of the Singleton Design Pattern or IoC container.

Introduction

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 how you can add a default behavior to your strategies. However, the usage of the strategies' context was tedious because its constructor accepted many dependencies that needed to be initialized manually. Here I will show you, how you can improve the usage of the Null Object Design Pattern through the Singleton Design Pattern and Unity IoC container.

Image 1

Immutable Strategies via Base Singleton Implementation

If you have not already read my previous article about the Null Object Design Pattern, I suggest you to do so. The problem that I am going to solve here is the tedious initialization of the strategies' context. Another thing that can be improved is the performance. It makes sense to create just a single immutable instance of the NULL class. Because the NULL class just has default behavior, it makes more sense to cache it.

One way to do it is to create a base class that contains inside of it the Null object.

C#
public abstract class BasePromotionalCodeStrategy : IPurchasePromotionalCodeStrategy
{
    public abstract void AssertPromotionalCodeDiscount();

    public abstract double GetPromotionalCodeDiscountAmount();

    public abstract void ApplyPromotionalCode(string couponCode);

    #region NULL

    static readonly NullPurchasePromotionalCodeStrategy nullPurchasePromotionalCodeStrategy =
    new NullPurchasePromotionalCodeStrategy();

    public static NullPurchasePromotionalCodeStrategy NULL
    {
        get
        {
            return nullPurchasePromotionalCodeStrategy;
        }
    }

    public class NullPurchasePromotionalCodeStrategy : BasePromotionalCodeStrategy
    {
        public override void AssertPromotionalCodeDiscount()
        {
        }

        public override double GetPromotionalCodeDiscountAmount()
        {
            return 0;
        }

        public override void ApplyPromotionalCode(string couponCode)
        {
        }
    }
        
    #endregion
}

All strategies derive from this base class.

C#
public class UiPurchasePromotionalCodeStrategy : BasePromotionalCodeStrategy
{
    private readonly PlaceOrderPage placeOrderPage;
    private readonly double couponDiscountAmount;

    public UiPurchasePromotionalCodeStrategy(
        PlaceOrderPage placeOrderPage, 
        double couponDiscountAmount)
    {
        this.placeOrderPage = placeOrderPage;
        this.couponDiscountAmount = couponDiscountAmount;
    }

    public override void AssertPromotionalCodeDiscount()
    {
        Assert.AreEqual(
            this.couponDiscountAmount.ToString(),
            this.placeOrderPage.PromotionalDiscountPrice.Text);
    }

    public override double GetPromotionalCodeDiscountAmount()
    {
        return this.couponDiscountAmount;
    }

    public override void ApplyPromotionalCode(string couponCode)
    {
        this.placeOrderPage.PromotionalCode.SendKeys(couponCode);
    }
}

The difference with the previous implementation is that we do not have a separate file with the NullPurchasePromotionalCodeStrategy. The NULL property can be accessed by every child of the BasePromotionalCodeStrategy class. Below, you can find how one sample test will look.

C#
[TestMethod]
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(
        UiPurchasePromotionalCodeStrategy.NULL,
        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);
}

This is a convenient approach if you use the NULL object in more than one test because its instance will be reused among them.

Image 2

Improve Null Object Design Pattern through Pure Singleton Design Pattern Implementation

In my opinion, nested classes or files containing more that one class is not a best practice. Because of that, I like more the pure implementation of the Singleton Design Pattern. Since most of the strategies need different parameters for every test, it doesn't make sense to be implemented as singletons. For the given examples, I think this is appropriate only for the NULL object.

For this implementation, we don't need the base class anymore, the singleton's code can be placed only in the NullPurchasePromotionalCodeStrategy class.

C#
public class NullPurchasePromotionalCodeStrategy : IPurchasePromotionalCodeStrategy
{
    private static NullPurchasePromotionalCodeStrategy instance;

    public static NullPurchasePromotionalCodeStrategy NULL
    {
        get
        {
            if (instance == null)
            {
                instance = new NullPurchasePromotionalCodeStrategy();
            }
            return instance;
        }
    }

    public void AssertPromotionalCodeDiscount()
    {
    }

    public double GetPromotionalCodeDiscountAmount()
    {
        return 0;
    }

    public void ApplyPromotionalCode(string couponCode)
    {
    }
}

The usage in tests is identical to the previous approach, but the code is cleaner.

C#
[TestMethod]
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(NullPurchasePromotionalCodeStrategy.NULL,
        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);
}

Improve Null Object Design Pattern Further through Unity IoC Container

As you can see from the last example, the initialization of the PurchaseContext is a real pain. If you have read my article about Unity IoC Container- Use IoC Container to Create Page Object Pattern on Steroids, you most probably have figured out what I meant with the title of this section. We can use almost all of the dependency objects as singletons since they don't have any state. Also, Unity can resolve all of them for us- recursively.

The first thing you need to do is to register all required types in the container.

C#
private static IUnityContainer container = new UnityContainer();

[TestInitialize]
public void SetupTest()
{
    Driver.StartBrowser();
    container.RegisterType<ItemPage>(new ContainerControlledLifetimeManager());
    container.RegisterType<PreviewShoppingCartPage>(new ContainerControlledLifetimeManager());
    container.RegisterType<SignInPage>(new ContainerControlledLifetimeManager());
    container.RegisterType<ShippingAddressPage>(new ContainerControlledLifetimeManager());
    container.RegisterType<ShippingPaymentPage>(new ContainerControlledLifetimeManager());
    container.RegisterType<PlaceOrderPage>(new ContainerControlledLifetimeManager());
    container.RegisterType<PurchaseContext>(new ContainerControlledLifetimeManager());
    container.RegisterType<
    IPurchasePromotionalCodeStrategy, 
    NullPurchasePromotionalCodeStrategy>(new ContainerControlledLifetimeManager());
    container.RegisterInstance<IWebDriver>(Driver.Browser);
}

When you pass an instance of ContainerControlledLifetimeManager to the RegisterType method, you configure Unity to resolve your type as a singleton. All pages have only a single implementation and no interface, because of that we register them directly. This is not valid for the promotional code's strategies. Therefore, we tell Unity to resolve the IPurchasePromotionalCodeStrategy as our Null Object Design Pattern implementation by default. Through the last line of the method, we register the already created instance of WebDriver. Most of this code can be moved to the AssemblyInitialize method of our test project.

Resolve Null Object through Unity IoC Container

C#
[TestMethod]
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 = container.Resolve<PurchaseContext>();
    purchaseContext.PurchaseItem(itemUrl, itemPrice, clientLoginInfo, clientPurchaseInfo);
}

As you can see, the code of the test is shorter and more readable thanks to the Unity container. Moreover, all pages and all other dependencies will be reused in the rest of the tests.

Resolve Non-Null Object through Unity IoC Container

You can override the default strategy implementation for the promotional codes.

C#
[TestMethod]
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"
    };
    container.RegisterInstance<IPurchasePromotionalCodeStrategy>(
        new UiPurchasePromotionalCodeStrategy(container.Resolve<PlaceOrderPage>(), 40.49));
    var purchaseContext = container.Resolve<PurchaseContext>();
    purchaseContext.PurchaseItem(itemUrl, itemPrice, clientLoginInfo, clientPurchaseInfo);
}

Now if you run the test, you will notice that the UiPurchasePromotionalCodeStrategy class will be used in the strategies' context.

So Far in the "Design Patterns in Automated Testing" Series

  1. Page Object Pattern
  2. Advanced Page Object Pattern
  3. Facade Design Pattern
  4. Singleton Design Pattern
  5. Fluent Page Object Pattern
  6. IoC Container and Page Objects
  7. Strategy Design Pattern
  8. Advanced Strategy Design Pattern
  9. Observer Design Pattern
  10. Observer Design Pattern via Events and Delegates
  11. Observer Design Pattern via IObservable and IObserver
  12. Decorator Design Pattern- Mixing Strategies
  13. Page Objects That Make Code More Maintainable
  14. Improved Facade Design Pattern in Automation Testing v.2.0
  15. Rules Design Pattern
  16. Specification Design Pattern
  17. Advanced Specification Design Pattern
  18. Null Object Design Pattern

If you enjoy my publications, feel free to SUBSCRIBE
Also, hit these share buttons. Thank you!

The post Advanced Null Object Design Pattern in Automated Testing appeared first on Automate The Planet.

All images are purchased from DepositPhotos.com and cannot be downloaded and used for free.
License Agreement

License

This article, along with any associated source code and files, is licensed under The Microsoft Public License (Ms-PL)


Written By
CEO Automate The Planet
Bulgaria Bulgaria
CTO and Co-founder of Automate The Planet Ltd, inventor of BELLATRIX Test Automation Framework, author of "Design Patterns for High-Quality Automated Tests: High-Quality Test Attributes and Best Practices" in C# and Java. Nowadays, he leads a team of passionate engineers helping companies succeed with their test automation. Additionally, he consults companies and leads automated testing trainings, writes books, and gives conference talks. You can find him on LinkedIn every day.

Comments and Discussions

 
-- There are no messages in this forum --