Craftsman at Work

I'm Artur Karbone, coding software architect and independent IT consultant and this is my blog about craftsmanship, architecture, distributed systems, management and much more.

Read Your Functional Tests as a Book in C# Part 5: Leverage Fluent Interface

The Goal

In this series of posts I'm going to show how to leverage clean code principles when writing functional tests.

Fluent Interface

Navigation between flow steps is implemented via fluent interface, which is kind of syntactic sugar. At least the reader of the code has a feeling that the next step is naturally flowing from the previous one.

Our code under test, which I have mentioned many times in this series

  [TestFixture]
    public class BookForwarderScenario : BaseIntegrationTest
    {
        [Test]
        [ImpersonateRandomSystemPortalUser]
        public void BookSingleForwarderFlow()
        {            
           CustomerApp
                .NavigateToRegisterAndBook()
                .CreateBookForwarderContext()
                .ChooseFirstOrder()
                .LaunchBookingWizard()
                .SelectFirstForwarder()
                .GoToTheNextWizardStep()
                .SelectFirstContact()
                .GoToTheNextWizardStep()
                .SkipAgreedFreight()
                .Book()
                .EnsureNotification()
                .EnsureOrderHasGoneFromGrid()
                .GoToFollowUp()
                .EnsureBookedOrderExists();              
        }
    }

Non-fluent implementation example would look like:

...
CustomerApp.NavigateToRegisterAndBook();  
CustomerApp.CreateBookForwarderContext();  
CustomerApp.ChooseFirstOrder();  
CustomerApp.LaunchBookingWizard();  
...

Implementing fluent interface is not a big deal. The secret is that each method in the pipe line needs to return an instance of it's own class. Here are couple of methods, which make fluent pipe line possible:

    public class BookForwarderFlowContext : BaseFlowContext
    {
        ...
        public BookForwarderFlowContext ChooseFirstOrder()
        {
            OrderNumber = WebDriver
                .FindRegisterAndBookTable()
                .FindLink()
                .Text;

            WebDriver
                .FindRegisterAndBookTable()
                .FindFirstRow()
                .FindFirstVisibleCheckBox()
                .Click();
            return this;
        }

        public BookForwarderFlowContext BookTheOrder()
        {
            WebDriver
                .FindBookForwarderButton()
                .Click();
            return this;
        }

        ...
}

By the way, the context itself does not contain low-level WebDriver acrobatics. It is all hidden in extension methods. For instance, FindBookForwarderButton method looks like this:

   public static partial class ISearchContextExtensions
    {
        public static IWebElement FindBookForwarderButton(this ISearchContext element)
        {
            return element.FindButtonByText("Book Forwarder");
        }

That's all Folks

Posts in this series:

Part 1: The Tradegy

Part 2: Setting Up The Stage

Part 3: High Level of Abstraction

Part 4: A Context for Your Scenario

Part 5: Leverage Fluent Interface

comments powered by Disqus