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


4. Fill in the purchase info


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 NoTaxOrderPurchaseStrategy, CouponCodeOrderPurchaseStrategy 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 – VatTaxOrderPurchaseStrategy, CouponCodeOrderPurchaseStrategy, 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)
