Testing on the Toilet: Testing UI Logic? Follow the User!
pondelok, októbra 26, 2020
This article was adapted from a Google Testing on the Toilet (TotT) episode. You can download a printer-friendly version of this TotT episode and post it in your office.
Users couldn’t buy their gShoes because the “Buy” button was disabled. The problem was due to the unit test for handleBuyClick, which passed even though the user interface had a bug:
In the above example, the test failed to detect the bug because it bypassed the UI element and instead directly invoked the "Buy" button click handler. To be effective, tests for UI logic should interact with the components on the page as a browser would, which allows testing the behavior that the end user experiences. Writing tests against UI components rather than calling handlers directly faithfully simulates user interactions (e.g., add items to a shopping cart, click a purchase button, or verify an element is visible on the page), making the tests more comprehensive.
By Carlos Israel Ortiz García
After years of anticipation, you're finally able to purchase Google's hottest new product, gShoe*. But after clicking the "Buy" button, nothing happened! Inspecting the HTML, you notice the problem:
<button disabled click=”$handleBuyClick(data)”>Buy</button>
|
it('submits purchase request', () => {
controller = new PurchasePage();
// Call the method that handles the "Buy" button click
controller.handleBuyClick(data);
expect(service).toHaveBeenCalledWith(expectedData);
});
|
The test for the “Buy” button should instead exercise the entire UI component by interacting with the HTML element, which would have caught the disabled button issue:
it('submits purchase request', () => {
// Renders the page with the “Buy” button and its associated code.
render(PurchasePage);
// Tries to click the button, fails the test, and catches the bug!
buttonWithText('Buy').dispatchEvent(new Event(‘click’));
expect(service).toHaveBeenCalledWith(expectedData);
});
|
Why should tests be written this way? Unlike end-to-end tests, tests for individual UI components don’t require a backend server or the entire app to be rendered. Instead, these tests run in the same self-contained environment and take a similar amount of time to execute as unit tests that just execute the underlying event handlers directly. Therefore, the UI acts as the public API, leaving the business logic as an implementation detail (also known as the "Use the Front Door First" principle), resulting in better coverage of a feature.
Disclaimer: “gShoe” is not a real Google product. Unfortunately you can’t buy a pair even if the bug is fixed!
If I understand this correctly, you are adding an additional layer between UI and API testing (looking at the test automation pyramid)
OdpovedaťOdstrániťTo my understanding, automated tests on an API level (here a web request to the backend) should purely focus on whether the purchase works with differnt input coming from a potential "non-trustworthy" client. Does it really matter at this point, whether the button is broken or not for all data driven tests in this regard?
Why is this particular test (button disabled or not under the conditions given) not addressed in a pure UI test?
T.
I think what you're saying is essentially what the ToTT is trying to convey, if you write UI you will test UI.
OdstrániťThe inspiration for the ToTT is that many developers who write UI don't test UI components, instead they test "controller instances", especially those who come from a backend background. In Java world you test your `new Stuff()` but in UI you render it and interact with it.
There is no underlying backend, you would mock/fake out those dependencies on either strategy. Otherwise this would be e2e testing.
Another way of putting it is (based on Dave Cheney's unit testing talk), to write unit tests you must define a unit. In UI your unit is your entire component (JS + HTML template) and not just the controller (JS).
I argue that the same principle applies to native UI like Desktop or Android apps, but rendering may not be as simple as web with headless browsers, although some alternative based on the same idea exist.
Hello,
OdpovedaťOdstrániťI would like to understand it better, how you render your component with JS+HTML without open the browser?
You do open a headless browser, so rather than a nodejs runtime your runtime is this headless browser where rendering is possible
OdstrániťWhy not testing them separately a better thing?
OdpovedaťOdstrániť1. Test the API, all values, invalid, fuzzy, auth, etc.
2. Ensure that button has the right callback assigned (to call API in #1)
3. Ensure that clicking the UI button calls the callback.
The key difference is that #3 is done through mocking, where you assert that the button called your mock. You do not involve the real API at this point.
Rationale: You already asserted that calling the API works (tested in #1). So what you need left is to test: (a) did you call the API; and (b) did you handle correctly the results of such call.
#3 already tests #2, so no need to separate them, I agree with #1 being separate. That's essentially this post's intention :)
Odstrániť