This article is a way for me to sort my thoughts on method chaining, its pros and cons. Why I liked it, and lately I am more and more negative toward it.
Why this subject?
So as you may remember from my previous article, I was using in that project method chaining.
However, lately, I see it using more and more in a way that is more harmful than useful.
That is why I want to discuss Method Chaining. Note I this article I am again giving the cases from UI testing world.
Because this is the place, I have seen it overused most — still a lot of the advantages and issues I’ve described here applies to any code.
First, what is Method Chaining:
Method chaining is a way of creating a method so they return an object in a way that you can then perform next action on it without „Breaking the chain.”
So let’s say we want to perform a search for a product that cost between 10 and 50 euro, belongs to category games and has metal in name
normally you would do something like it.
SearchPage searchPage = new SearchPage(this.Driver); searchPage.SetMinimalPrice(10.0); searchPage.SetMaximumPrice(50.0); searchPage.SetCategory("games"); searchPage.SetName("Metal"); SearchResultsPage searchResultPage = searchPage.ClickSearchButton();
With method chaining, it looks like this.
SearchResultsPage searchResultPage = new SearchPage(this.Driver) .SetMinimalPrice(10.0) .SetMaximumPrice(50.0) .SetCategory("games") .SetName("Metal") .ClickSearchButton();
There are a few reasons you may want to do it.
- One readability this is the main benefit of method chaining – it makes code a more readable. As you can see the „searchPage”
didn’t need to be repeated on every line. However, this point is also the most arguable. Not everybody agrees with this fact. I also have an issue with it. - The IntelliSense – after each Method you call, you can use IntelliSense to suggest the following methods to call.
- Creation of Sudo DSL – use full when different people are writing test itself, and different people are preparing the page objects.
Fluent interfaces vs method chaining
There is one mistake that I am also prone to make. That why I need to clarify:
Method Chaining is not a Fluent Interface!
Yes, Fluent Interface can use Method Chaining, but there goes much more into it then method chaining.
I won’t go into full detail here. Just remember you can have fluent interfaces with and without Method chaining.
Even when FI uses it, there goes much more into designing the interfaces, then returning the object.
If you want to know more, you can check this post by Martin Fowler and this answer on stack overflow.
So What is my issue with it?
Let’s go to my previous example.
I don’t want to break the chain, and I want to return several rows in the result.
To do that I would have to use the out parameter.
SearchResultsPage searchResultPage = new SearchPage(this.Driver) .SetMinimalPrice(10.0) .SetMaximumPrice(50.0) .SetCategory("games") .SetName("Metal") .ClickSearchButton() .GetResultsCount(out int count) .SomeOtherAction();
Of course, we could make some other decision here like breaking the chain.
So we could use method chaining with CommandQuerySeparation.
In this case, the Command returns the object instead of void.
SearchResultsPage searchResultPage = new SearchPage(this.Driver) .SetMinimalPrice(10.0) .SetMaximumPrice(50.0) .SetCategory("games") .SetName("Metal") .ClickSearchButton(); var count = searchResultPage.GetResultsCount(); searchResultPage.SomeOtherAction() .AndAnother();
This has its problems. If I want to continue chain after returning value, I have to do break at least a row before getting the value. I still prefer this over using out.
So what with that readably
As with everything, it can be overused. Unfortunately, I have seen overuse of Method Chaining quite a few times lately.
new SearchPage(this.driver) .InputType("cake") .InputCategory("wedding") .InputMinimalPrice(100) .InputMinimalPrice(10000) .ClickVegan(false) .ClickDiaryFree(false) .ClickFreeDeliver(true) .ClickSearchButton .GetResultsCount(out int count) .ClickOnItemInResults("Cake") .SetColour("red") .SetNumberOfLayers(10) .AddLayer() .SetLayerType("Biscuit") .Confirm() .AddLayer() .SetLayerType("ToffeCreem") .Confirm() .AddLayer() .SetLayerType("Waffle") .Confirm() .AddLayer() .SetLayerType("ChoclateCream") .Confirm() .AddLayer() .SetLayerType("Biscuit") .Confirm() .AddLayer() .SetLayerType("ToffeCreem") .Confirm() .AddLayer() .SetLayerType("Waffle") .Confirm() .AddLayer() .SetLayerType("Cream") .Confirm() .AddLayer() .SetLayerType("Biscuit") .Confirm() .AddLayer() .SetLayerType("Browne") .Confirm() .SelectTopping("Choclate") .SelectTopping("Toffee") .SelectTopping("Falafe") .SelectTopping("Are your reading this?") .AddToCart() .GoToCart() .SelectDeliveryDate("ASAP") .InsertDeliveryAdressPostalCode(00-000) .InsertDeliveryAdressCity("nonexitsia") .InsertDeliveryAdressAdress("adress") .Confirm() .PayWithPayPal() .Login() .Pay() .GetConfrimation(out ConfirmAtionDetails details);
Note: This example is entirely fake I came up here on the spot, there is no point in overthinking it, what is important the number of lines)
Ok, there are many issues here.
First, I time to address the elephant in the room:
Yes The test length itself is a huge issue.
Same for Overall design of the test.
Both topics are big enough to deserve their own articles.
MethodChainig enabled the author of this code to write this monster and believe me I seen much longer ones.
Well, Method Chaining is not to blame the way for how others use it.
I am a fan of the old rule that *No method should be larger than one screen. – Lately due to huge screens I prefer more specific limit: max 60 lines per Method.
Similarly, the chain has its limit while doing my research; I found a number 8 to 11 mentioned. My opinion on a number is still forming for now I am more inclined to limit of 5 for Tests.
Rules for Method Chaining
Another thing while doing the research I found this helpful summary
- Try not to chain between classes
- Make routines specifically for Chaining
- Do only ONE thing in a chaining routine
- Use it when it improves readability
- Use it when it makes code simpler
Its agrees with the way I was using Method Chaining, but I wasn’t able to explain it so well to others.
Let’s Check rules on our code
Make routines specifically for Chaining – We have this one covered.
Do only ONE thing in a chaining routine – I doubt that above code dose well here.
In one way you can think that we are doing one thing – we are performing the actions of a user. – yes, it is a little stretch.
Try not to chain between classes. – This one is broken.
Use it when it improves readability – this one is also broken.
Let’s try to fix it.
SearchPageResult searchResultPage = new SearchPage(this.driver) .InputType("cake") .InputCategory("wedding") .InputMinimalPrice(100) .InputMinimalPrice(10000) .ClickVegan(false) .ClickDiaryFree(false) .ClickFreeDeliver(true) .ClickSearchButton; CakeOrderPage cakePage = searchResultPage.GetResultsCount(out int count) .ClickOnItemInResults("Cake"); OrderPage orderPage = cakePage.SetColour("red") .SetNumberOfLayers(10) .AddLayer() .SetLayerType("Biscuit") .Confirm() .AddLayer() .SetLayerType("ToffeCreem") .Confirm() .AddLayer() .SetLayerType("Waffle") .Confirm() .AddLayer() .SetLayerType("ChoclateCream") .Confirm() .AddLayer() .SetLayerType("Biscuit") .Confirm() .AddLayer() .SetLayerType("ToffeCreem") .Confirm() .AddLayer() .SetLayerType("Waffle") .Confirm() .AddLayer() .SetLayerType("Cream") .Confirm() .AddLayer() .SetLayerType("Biscuit") .Confirm() .AddLayer() .SetLayerType("Brownie") .Confirm() .SelectTopping("Choclate") .SelectTopping("Toffee") .SelectTopping("Falafe") .SelectTopping("Are your reading this?") .AddToCart() .GoToCart(); PaymentPage paymentPage = orderPage.SelectDeliveryDate("ASAP") .InsertDeliveryAdressPostalCode(00-000) .InsertDeliveryAdressCity("nonexitsia") .InsertDeliveryAdressAdress("adress") .Confirm(); PayPalPage payPalPAge = paymentPage.PayWithPayPal(); SummaryPage sumnaryPage = payPalPAge.Login() .Pay() .GetConfrimation(out ConfirmAtionDetails details);
Much better – except for creating the cake. Again for now let’s ignore why someone would do a test like that.
Let’s take care of the next point:
„Use it when it makes code simpler”.
The Irony here is that in my example is Method Chaining as part of fluent interfaces is used for object creation. Here we have problems with creating a cake.
This is another discussion, but it is connected with my previous article and interaction between different rules.
So let’s assume we set the rule: „page methods simulate user actions”. To create a cake layer, a user has to add a layer, select its type and then confirm it.
So, in theory, the user does 3 actions. That a lot a UX expert should look at it, but let’s say it has to be this way.
In that case, I would probably see it as one complex action. So instead of this.
.AddLayer() .SetLayerType("Biscuit") .Confirm()
I would create a facade that covers these actions as one.
AddLayer("Biscuit")
Of course, inside of it, I would call the 3 methods from the previous example.
It’s better but still bad.
I have a few ideas on how I would address it.
However, here we are outside of the scope of the article cause we would need to examine all rulers we set up for how we write tests.
The last problem with Method Chaining Is what I discussed in the previous article. It can lead to code redundancy.
I won’t repeat my self here if you want to read more, please check that article.
Conclusion
This was a huge wall of text.
To sum it up.
Method Chaining is a good way for readability.
However, it can lead to problems, so before using it, you need to establish some rules.
Especially when you are using it in the test,
As with everything its good to understand what we are using, why, and what consequences it brings.
We don’t want golden hammers.
.
What about you? Do you use Method Chaining? If Yes, then why and when?
Sources:
https://stackoverflow.com/a/6076476
https://stackoverflow.com/questions/1103985/method-chaining-why-is-it-a-good-practice-or-not
https://ocramius.github.io/blog/fluent-interfaces-are-evil/ – note – I have feeling that author’s problem is actually Method Chaining not the Fluent Interfacs.
https://scottlilly.com/how-to-create-a-fluent-interface-in-c/
https://www.codeproject.com/Articles/99542/Guidelines-to-Fluent-Interface-design-in-C-Part-1
https://www.codeproject.com/Articles/99541/Guidelines-to-Fluent-Interface-design-in-C-Part-2
https://www.codeproject.com/Articles/100441/Guidelines-to-Fluent-Interface-Design-in-Csharp-Pa
https://stackoverflow.com/questions/293353/fluent-interfaces-method-chaining
Pingback: Testing Bits – August 18th – August 24th, 2019 | Testing Curator Blog
From my experience, I would add that method chaining in test automation is acceptable only in small, in-memory, operations. E.g. in case of builder pattern implementation, where you have full control over behavior and logic hidden within the chained methods and do not call 3rd party API, e.g. performing Selenium actions.
Otherwise – it would dramatically decrease maintainability of your code and make debugging much harder. For those reasons I consider it as a test code smell and avoid it as much as possible.