Design Pattern – Facade

Design Pattern - Factory

In the last article of this series, we learnt how to write tests using the Page Object Model design pattern. It allowed us to create a test with minimal code, keeping all the actions and element interactions in our page object class itself. This works brilliantly with web applications that have no set journey they have to follow, but in instances where you do have these journeys, it can often mean you start to lose the benefit of the Page Object Model inside your tests. To get round this, there is another design pattern that we can look at, the Facade pattern.

We touched upon it briefly in the very first article of the series, but let’s expand upon what is by using an example. Take your favourite online retailer, whenever you add something to your basket and go to checkout, you pretty much always follow the same journey, no matter the site. Enter your details, your delivery details, your payment details then finally confirm. Now if we were to write a test to make sure the checkout journey is working correctly, and we wanted to use the Page Object Model. Our page object classes would still be as efficient as before, but the trouble is with this is we are navigating across multiple pages, we now have to create an object for each of our pages, and then use different objects depending on the page we’re using. Suddenly our nice and simple tests become a little more complicated. Lets look at a code example to see how this might look.

For this example, we have created a page object class for PersonalDetailsPage, DeliveryDetailsPage, PaymentDetailsPage and ConfirmationPage.

[Test]
public void CustomerCanCompletePurchase()
{
	PersonalDetailsPage personalDetailsPage = new PersonalDetailsPage(this.Driver);
	DeliveryDetailsPage deliveryDetailsPage = new DeliveryDetailsPage(this.Driver);
	PaymentDetailsPage paymentDetailsPage = new PaymentDetailsPage(this.Driver);
	ConfirmationPage confirmationPage = new ConfirmationPage(this.Driver);
	
        personalDetailsPage.EnterPersonalDetailsAndProceed();
	deliveryDetailsPage.EnterDeliveryDetailsAndProceed();
	paymentDetailsPage.ChoosePaymentMethod(PaymentType.CreditCard);
	paymentDetailsPage.EnterPaymentDetails();
	Assert.IsTrue(confirmationPage.ConfirmOrderDetails(), "Order details did not match the expected ones");
}

As you can see, suddenly we are having to create four objects just for our test to be possible, and then we switch between objects dependent on the page we’re currently on. We could of course break these tests in to smaller tests for each page, but this becomes a very fragile set of tests when they all rely on each other.

So how does Facade get around this problem? Well Facade doesn’t do anything radical, in fact it will keep our individual page objects classes exactly as they are. We simply add a new class that wraps all these classes up in to one that is designed to deal with this checkout journey.

public class PurchaseFacade
{
    private PersonalDetailsPage personalDetailsPage;
    private DeliveryDetailsPage deliveryDetailsPage;
    private PaymentDetailsPage paymentDetailsPage;
    private ConfirmationPage confirmationPage;

    public PersonalDetailsPage PersonalDetailsPage 
    {
        get
        {
            if (personalDetailsPage == null)
            {
                personalDetailsPage = new PersonalDetailsPage();
            }
            return personalDetailsPage;
        }
    }

    public DeliveryDetailsPage DeliveryDetailsPage
    {
        get
        {
            if (deliveryDetailsPage == null)
            {
                deliveryDetailsPage = new DeliveryDetailsPage();
            }
            return deliveryDetailsPage;
        }
    }

    public PaymentDetailsPage PaymentDetailsPage
    {
        get
        {
            if (paymentDetailsPage == null)
            {
                paymentDetailsPage = new PaymentDetailsPage();
            }
            return paymentDetailsPage;
        }
    }

    public ConfirmationPage ConfirmationPage
    {
        get
        {
            if (confirmationPage == null)
            {
                confirmationPage = new ConfirmationPage();
            }
            return confirmationPage;
        }
    }

    public bool CompletePurchase()
    { 	
	    personalDetailsPage.EnterPersonalDetailsAndProceed();
	    deliveryDetailsPage.EnterDeliveryDetailsAndProceed();
	    paymentDetailsPage.ChoosePaymentMethod(PaymentType.CreditCard);
	    paymentDetailsPage.EnterPaymentDetails();
	    return confirmationPage.ConfirmOrderDetails();
    }
}

I’m sure you’ve noticed that our CompletePurchase method looks identical to our test, but that’s the beauty of Facade in a situation like this. Suddenly we have a method to deal with our whole journey, so in our test we have to simply call this one method using a single object of our PurchaseFacade class. And now we’re right back to having a simple test like we had in the previous article.

But just to stress how simple it is, here’s our new test. Doing the exact same thing as the previous test using four objects, but now using our Facade class.

[Test]
public void CustomerCanCompletePurchase()
{
	PurchaseFacade purchaseFacade = new PurchaseFacade(this.Driver);

	Assert.IsTrue(purchaseFacade.CompletePurchase(), "Order details did not match the expected ones");
}

Isn’t it simple?

The beauty of Facade is it can condense tests that navigate across multiple pages in to a single class. But I’m going to stop short of recommending it all the time. I think the use case has to warrant a Facade design pattern. There’s no point having Facade classes for every possibly journey a customer might follow in your system, as then you start having overlap in your classes and it just becomes bad design. Think of the scenarios that it might benefit from the most. Journeys like a customer purchase, sign up to a website or applying for a service. These are a few examples where it makes sense and could be used.

But now you understand what Facade is and how it can benefit your tests. In the next article we’ll look at the Factory design pattern and how that can work to handle dynamic content in tests.