The entry sparked a discussion on an internal ThoughtWorks mailing list as to whether Page Objects should include assertions or not. In fact Martin mentions this difference of opinion in the bliki entry itself. I fell on the side of favoring page objects with assertions baked in but couldn’t come up with a compelling reason why until I was working on some Capybara-based page objects with a client QA today.
If you don’t put assertions inside your page objects you are violating Tell Don’t Ask, which in turn hinders the ability for your testing framework to do implicit spin asserts - a very valuable feature.
In the following I’ll explain all of that in more detail.
Tell Don’t Ask vs Single Responsibility Principle
Let’s use a concrete example with Page Objects. I’m testing a web page which lists my bank accounts. In one of my tests I want to verify that the list of bank accounts includes a bank account with a balance of 100.25.
If I didn’t want assertions in my page object I would Ask my page object for a list of bank accounts, and then check for the balance myself:
On the other hand if I’m OK with assertions in my page object I would Tell my page object to check for a listed bank account with that balance:
This second example conforms to Tell Don’t Ask, the idea that it’s more object-oriented to tell an object what you want it to do, rather than to ask it questions about its state and then do the work yourself. However you could argue that the second page object example with embedded assertions also violates the Single Responsibility Principle - my page object is now responsible for both abstracting over the page being tested and also performing assertions on that page’s state.
At this point we see there are valid arguments both for and against the inclusion of assertions in our page objects. This illustrates that software design is rarely about right and wrong, but almost always about trade-offs between different design choices. However today I realized that there’s a feature of Capybara (and other similar UI testing frameworks) which in my opinion pushes the decision further towards complying with Tell Don’t Ask and including the assertions in the page object.
Spin Assert is a very valuable UI automation pattern. It is used to mitigate the fact that UI automation tests are often involved in races with the UI they are testing.
Let’s continue using our account listing example. Imagine I have a test in which I transfer some money from my checking account to my savings account, and then test that the balances are listed correctly:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
Looks good. But what if the account balances take a while to update in the UI after the funds are transfered? My test might check that the checking account has a balance of 100.25 before the UI has had a chance to update. The test would see a balance of 200.25 instead of the 100.25, and thus the test would fail before the UI has had a chance to update to the correct state. The test has raced ahead of the UI, leading to an invalid test failure.
Spin Asserts deal with this by repeatedly checking for the expected state, rather than checking once and then summarily failing. Here’s what a crude spin assert implementation might look like in a
1 2 3 4 5 6 7
Here we just repeatedly search for an account with the expected name and balance, pausing briefly each time we loop. In a real spin assert implementation we would eventually time out and raise an exception to fail the test after spinning unsuccessfully for a while.
Implicit Spin Asserts
Spin Asserts are such a valuable technique that they is build into a lot of web automation tools. In fact a lot of web automation tools will do a spin assert without you even being aware of it. In Selenium/WebDriver this is referred to as ‘implicit waits’, and Capybara does the same thing.
When we tell Capybara “check that this HTML node contains the text ‘foo’” it will implicitly perform that check using a spin assert. It will repeatedly check the state of the UI until either the node in question contains the text ‘foo’ or the spin assert times out. Here’s what that assertion might look like:
I think it’s pretty neat that a spin assert is hidden in there for free. However what if we instead ask Capybara for the current content of an HTML node and then do further checks ourselves?
This looks very similar, but now we’re asking for
.text instead of telling Capybara that we want the text to contain ‘foo’. Capybara is required to return the full content of the node at the instant we ask for
.text, which robs it of the chance to do the helpful implicit spin asserts it could do if we were telling it to check for ‘foo’.
By violating Tell Don’t Ask we’re reducing Capybara role to dumbly exposing state and prevented it from enhancing that state with value-add behavior.
Page Objects should include Assertions
Hopefully you can see where I’m heading by now. If our example account listing page object includes its own assertions then we can tell the page object “verify that there is a checking account with a balance of 100.25”. The page object is free to internally use spin asserts while verifying that assertion. If we don’t include assertions within our page objects then we are required to verify the page state externally. This would mean we’d need to do implement our own spin asserts, and often wouldn’t be able to take advantage of the free implicit spin asserts provided by Capybara. An example which highlights the difference this can make is asserting that we’re on the correct page by checking the page title.
With internal assertions in our page object:
1 2 3 4 5 6 7 8 9 10 11
As opposed to external assertions:
1 2 3 4 5 6 7 8 9 10 11
In the first example we get Capybara’s implicit spin asserts for free. In the second example not only are we’re required to do our own explicit spin assert, but we’re required to do so every time we want to verify the page has the right title. Overly verbose and prone to error.
Tell Don’t Ask allows value-add behavior
There are other advantages we get from including assertions in our page objects (for example better assertion failure messages), but for me the ability to leverage implicit spin asserts is the big win. At the end of the day it means that a page object can present a higher level of abstraction in its public interface, adding value underneath. Yes, we’re weaker on Single Responsibility, but overall I think it’s a good tradeoff.