Advanced Strategy Design Pattern in Automated Testing Java Code

Advanced Strategy Design Pattern in Automated Testing Java Code

In this part of the Design Pattern Series, I’m going to extend my ideas about the application of the Strategy Design Pattern in automation tests. In my previous examples, I presented to you code samples where the tests used only one strategy at a time. Here I’m going to show you how to apply the pattern to use multiple strategies at once. Another advanced usage that I want to demonstrate is the Strategy Design Pattern’s use to assert the test prerequisites are correct.

In my last article Strategy Design Pattern, I explained the benefits of the application of Strategy Design Pattern in your automation tests. Some of the advantages are more maintainable code, encapsulated algorithm logic, easily interchangeable algorithms and less complicated code.

If you are not familiar with the above pattern, I suggest you to read my articles about them first, to be able to understand the presented concepts thoroughly.

Test’s Test Case

Bellatrix Demos Item Page

Bellatrix Demos View Shopping Cart Button

4. Fill in the purchase info

Bellatrix Demos Purchase Info Fill

Bellatrix Demos Assert Total Price

The difference between this article’s examples and the previous ones is going to be that there is a need sometimes to mix multiple strategies in one test. For instance buy an Online Store item with different billing and shipping info and so paying VAT and Sales taxes in a single purchase. Or even add a gift wrap to the same shopping cart. In my last post’s examples, these operations have been isolated in different strategy classes. The primary goal of today’s refactoring is going to be to extend the code to be able to operate with multiple strategy classes at once.

I have slightly modified the strategy interface with the addition of a new validation method.

public interface OrderPurchaseStrategy {
  void assertOrderSummary(double itemPrice, PurchaseInfo purchaseInfo);
  void validatePurchaseInfo(PurchaseInfo purchaseInfo);
}

This method is going to be used to validate the test prerequisite data passed to the pattern’s class. In VatTaxOrderPurchaseStrategy, if the shipping country part of the countries that have value-added tax, an IllegalArgumentException is thrown because the other class methods won’t be meaningful.

public class VatTaxOrderPurchaseStrategy implements OrderPurchaseStrategy {

  private final VatTaxCalculationService vatTaxCalculationService;

  public VatTaxOrderPurchaseStrategy() {
    vatTaxCalculationService = new VatTaxCalculationService();
  }

  @Override
  public void assertOrderSummary(double itemPrice, PurchaseInfo purchaseInfo) {
    var currentCountry = Arrays
      .stream(Country.values())
      .filter(country -> country.toString().equals(purchaseInfo.getCountry()))
      .toArray(Country[]::new)[0];
    var vatTax = vatTaxCalculationService.calculate(
      itemPrice,
      currentCountry,
      purchaseInfo
    );
    var checkoutPage = new CheckoutPage();
    Driver.waitForAjax();
    Driver.waitUntilPageLoadsCompletely();
    checkoutPage.assertions().assertOrderVatTaxPrice(vatTax);
  }

  @Override
  public void validatePurchaseInfo(PurchaseInfo purchaseInfo) {
    if (
      !Arrays.asList(VatCountry.values()).contains(purchaseInfo.getCountry())
    ) {
      throw new IllegalArgumentException(
        "If VatTaxOrderPurchaseStrategy is used, country should be set to one of the VAT countries because otherwise no VAT is going to be applied."
      );
    }
  }
}

The same logic is implemented in the NoTaxOrderPurchaseStrategyCouponCodeOrderPurchaseStrategy strategies. The only addition to these classes is the validatePurchaseInfo method. It can be used to validate that your strategies are utilized in the correct manner. There are many companies where quality assurance automation engineers write the core framework (strategies, context classes) while less technical people write the tests. So such preventative measures can be considered in cases like that.

Reconstructing the Purchase Context

First Version Basic Strategy Design Pattern Applied

public class PurchaseContext {

  private final OrderPurchaseStrategy orderPurchaseStrategy;
  private final ItemPage itemPage;
  private final ShoppingCartPage shoppingCartPage;
  private final CheckoutPage checkoutPage;

  public PurchaseContext(OrderPurchaseStrategy orderPurchaseStrategy) {
    this.orderPurchaseStrategy = orderPurchaseStrategy;
    itemPage = new ItemPage();
    shoppingCartPage = new ShoppingCartPage();
    checkoutPage = new CheckoutPage();
  }

  public void purchaseItem(
    String itemUrl,
    double itemPrice,
    PurchaseInfo purchaseInfo
  ) {
    itemPage.navigate(itemUrl);
    itemPage.clickBuyNowButton();
    itemPage.clickViewShoppingCartButton();
    shoppingCartPage.clickProceedToCheckoutButton();
    checkoutPage.fillBillingInfo(purchaseInfo);
    orderPurchaseStrategy.assertOrderSummary(itemPrice, purchaseInfo);
  }
}

Improved Version Advanced Strategy Design Pattern Applied

public class PurchaseContext {

  private final OrderPurchaseStrategy[] orderPurchaseStrategies;
  private final ItemPage itemPage;
  private final ShoppingCartPage shoppingCartPage;
  private final CheckoutPage checkoutPage;

  public PurchaseContext(OrderPurchaseStrategy... orderPurchaseStrategies) {
    this.orderPurchaseStrategies = orderPurchaseStrategies;
    itemPage = new ItemPage();
    shoppingCartPage = new ShoppingCartPage();
    checkoutPage = new CheckoutPage();
  }

  public void purchaseItem(
    String itemUrl,
    double itemPrice,
    PurchaseInfo purchaseInfo
  ) {
    validatePurchaseInfo(purchaseInfo);
    itemPage.navigate(itemUrl);
    itemPage.clickBuyNowButton();
    itemPage.clickViewShoppingCartButton();
    shoppingCartPage.clickProceedToCheckoutButton();
    checkoutPage.fillBillingInfo(purchaseInfo);
    validateOrderSummary(itemPrice, purchaseInfo);
  }

  public void validatePurchaseInfo(PurchaseInfo purchaseInfo) {
    for (var currentStrategy : orderPurchaseStrategies) {
      currentStrategy.validatePurchaseInfo(purchaseInfo);
    }
  }

  public void validateOrderSummary(
    double itemPrice,
    PurchaseInfo purchaseInfo
  ) {
    for (var currentStrategy : orderPurchaseStrategies) {
      currentStrategy.assertOrderSummary(itemPrice, purchaseInfo);
    }
  }
}

There are a couple of significant changes in the above code compared to the first version. The most prominent one is that the strategy instances are now stored in an array.

An unspecified count of strategies can be passed to the class’s constructor as a result of the usage of the operator after the method’s argument type.

public PurchaseContext(OrderPurchaseStrategy... orderPurchaseStrategies) {
    this.orderPurchaseStrategies = orderPurchaseStrategies;
    itemPage = new ItemPage();
    shoppingCartPage = new ShoppingCartPage();
    checkoutPage = new CheckoutPage();
}

Two new methods are added, where for each registered strategy, a particular method is executed. If you pass two strategies, their two functions are going to be performed successively.

The ValidatePurchaseInfo is called for each strategy so that if there are any misconfigured data, it will throw an IllegalArgumentException.

Advanced Strategy Design Pattern – Usage in Tests

First Version Basic Strategy Pattern Applied

public class StorePurchaseStrategyTests {

  @BeforeMethod
  public void testInit() {
    Driver.startBrowser();
  }

  @AfterMethod
  public void testCleanup() {
    Driver.stopBrowser();
  }

  @Test
  public void totalPriceCalculatedCorrect_when_AtCheckoutAndStrategyPatternUsed() {
    var itemUrl = "falcon-9";
    var itemPrice = 50.00;
    var purchaseInfo = new PurchaseInfo();
    purchaseInfo.setEmail("info@berlinspaceflowers.com");
    purchaseInfo.setFirstName("Anton");
    purchaseInfo.setLastName("Angelov");
    purchaseInfo.setCompany("Space Flowers");
    purchaseInfo.setCountry("Germany");
    purchaseInfo.setAddress1("1 Willi Brandt Avenue Tiergarten");
    purchaseInfo.setAddress2("Lützowplatz 17");
    purchaseInfo.setCity("Berlin");
    purchaseInfo.setZip("10115");
    purchaseInfo.setPhone("+491888999281");
    new PurchaseContext(new VatTaxOrderPurchaseStrategy())
      .purchaseItem(itemUrl, itemPrice, purchaseInfo);
  }
}

In the first version, it was possible to pass only a single instance of the OrderPurchaseStrategy.

Improved Version Advanced Strategy Design Pattern Applied

public class StorePurchaseAdvancedStrategyTests {

  @BeforeMethod
  public void testInit() {
    Driver.startBrowser();
  }

  @AfterMethod
  public void testCleanup() {
    Driver.stopBrowser();
  }

  @Test
  public void totalPriceCalculatedCorrect_when_AtCheckoutAndAdvancedStrategyPatternUsed() {
    var itemUrl = "falcon-9";
    var itemPrice = 50.00;
    var purchaseInfo = new PurchaseInfo();
    purchaseInfo.setEmail("info@berlinspaceflowers.com");
    purchaseInfo.setFirstName("Anton");
    purchaseInfo.setLastName("Angelov");
    purchaseInfo.setCompany("Space Flowers");
    purchaseInfo.setCountry("Germany");
    purchaseInfo.setAddress1("1 Willi Brandt Avenue Tiergarten");
    purchaseInfo.setAddress2("Lützowplatz 17");
    purchaseInfo.setCity("Berlin");
    purchaseInfo.setZip("10115");
    purchaseInfo.setPhone("+491888999281");
    purchaseInfo.setCouponCode("happybirthday");
    new PurchaseContext(
      new VatTaxOrderPurchaseStrategy(),
      new CouponCodeOrderPurchaseStrategy()
    )
      .purchaseItem(itemUrl, itemPrice, purchaseInfo);
  }
}

Here you can mix multiple strategies – VatTaxOrderPurchaseStrategyCouponCodeOrderPurchaseStrategy, and more. If you have to add new strategies to your logic, it will be easy to plug them into the existing design. You won’t have to modify the Context class because of the usage of the OrderPurchaseStrategy array and the  operator.

Summary

With only little code changes, we made it so we can check for multiple strategies at once, as well as check if they’re applied to the proper PurchaseInfo, while its usage in tests stays as smooth as before.

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, Java Edition, Clean Code for Bulletproof Tests”.  (+ with the book you will get an access to more than 20000+ lines of real-world code examples and video explanations to solidify your knowledge)

Related Articles

AutomationTools, Free Tools, Java

Quick Guide Bitbucket Pipelines on Running Selenium Java Tests

In this article from the series Automation Tools, I am going to guide you on how you can set up a Bitbucket Pipelines job for a Selenium Java project, run your

Quick Guide Bitbucket Pipelines on Running Selenium Java Tests

Java, Web Automation Java

30 Advanced WebDriver Tips and Tricks Java Code

This is the next article from the WebDriver Series where I will share with you 30 advanced tips and tricks using Java code. I wrote similar articles separated i

30 Advanced WebDriver Tips and Tricks Java Code

Java, Kotlin, Mobile Automation Java

Getting Started with Appium for Android Kotlin on macOS in 10 Minutes

The third article from the Appium Series is going to be about testing Android apps on macOS machine. I am going to show you how to configure your macOS machine

Getting Started with Appium for Android Kotlin on macOS in 10 Minutes

Java, Mobile Automation Java

Getting Started with Appium for Android Java on Windows in 10 Minutes

This is the first article from the new series dedicated to the mobile testing using Appium test automation framework. Here, I am going to show you how to config

Getting Started with Appium for Android Java on Windows in 10 Minutes

Java, Web Automation Java

Automate Telerik Kendo Grid with WebDriver with Java and JavaScript

Have you had this problem trying to automate custom-tuned web controls? Probably, your team has purchased these from some dedicated UI controls vendor. There ar

Automate Telerik Kendo Grid with WebDriver with Java and JavaScript

Design Architecture Java, Design Patterns Java, Java

Page Object Pattern in Automated Testing Java Code

In the series of articles Design Patterns in Automated Testing, I am presenting the most useful techniques for structuring the code of your automation tests. On

Page Object Pattern in Automated Testing Java Code
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.