Show TOC

Step 6: A First OPA TestLocate this document in the navigation structure

A bulletin board may contain many posts. We expect to have a high data load once it is officially released. Then, there might be performance issues and long loading times if we display all entries at the same time. Therefore we will introduce a feature that limits the initial display to 20 items. The user can then click on a more button to view more items. As with the unit test, we start by writing an integration test for this feature and then add the application functionality later.

Preview
Figure 1: The OPA test page is waiting for more items to be loaded
Coding

You can view and download all files in the Explored app in the Demo Kit under Testing - Step 6 .

Integration Test Setup

All integration tests are located in the webapp/test/integration folder and can be started manually by calling the opaTests.qunit.html file in the same folder or the entry page. Similar to the unit tests, the HTML page is a QUnit runner that calls all integration tests of the app and displays the test results in a readable format. It also might be omitted by other testrunners. There are also two namespaces defined for the app and the integration test folder as you have seen in the unit test setup.

We write integration tests with OPA5 – a tool that is integrated and delivered with SAPUI5. It is the short name for One-Page Acceptance tests for SAPUI5. "One-Page" here means that OPA5 is designed for single-page Web applications, i.e. applications that consist only of one HTML file. OPA5 runs in the same browser window as the application to be tested and opens a local iFrame to run the tests on the app.

Note

There is also a stand-alone version of OPA5 called “OPA” available that can be used for testing any kind of single-page Web application and that does not provide any SAPUI5-specific functionality. In this tutorial, “OPA” always refers to OPA5. It includes functionality for easily finding and matching SAPUI5 controls as well as their properties and aggregations.

Figure 2: Integration test infrastructure in the project

For structuring integration tests with OPA we use “journeys”. A test journey contains all test cases for a specific view or use case, for example the navigation journey simulates user interaction with the app.

The journey uses another structuring element of OPA called “page object” that encapsulates arrangements, actions, assertions needed to describe the journey. Typically those are related to a view in the app but there can also be stand-alone pages for browsers or common functionality.

webapp/test/integration/WorklistJourney.js
sap.ui.require(
	["sap/ui/test/opaQunit"],
	function (opaTest) {
		"use strict";
		QUnit.module("Posts");
		opaTest("Should see the table with all posts", function (Given, When, Then) {
			// Arrangements
			Given.iStartMyApp();
			//Actions
			When.onTheWorklistPage.iLookAtTheScreen();
			// Assertions
			Then.onTheWorklistPage.theTitleShouldDisplayTheTotalAmountOfItems();
		});
		opaTest("Should be able to load more items", function (Given, When, Then) {
			//Actions
			When.onTheWorklistPage.iPressOnMoreData();
			// Assertions

			Then.onTheWorklistPage.theTableShouldHaveAllEntries().
				and.iTeardownMyAppFrame();
		});
	}
);

Let’s add our first new OPA test to the WorklistJourney.js file. We describe all test cases related to the worklist logic. We can see that there is already a test Should see the table with all posts defined that checks if the table contains the expected number of items. There is a function opaTest that initiates a test description and receives a test description as the first argument as well as a callback function as the second argument. This format is similar to the unit test function QUnit.test except for the three arguments of the callback function that are specific to OPA.

The three objects Given, When, Then are filled by the OPA runtime when the test is executed and contain the arrangements, actions, and assertions for the test. The "Given-When-Then" pattern is a common style for writing tests in a readable format. To describe a test case, you basically write a user story. Test cases in this format are easy to understand, even by non-technical people.

Let’s give it a try with our new feature that only displays 20 posts in the table initially and will load more posts when we press a trigger button or scroll down. Here is our user story "Should see the table with all posts" and its code representation:
  • Arrangements

    Define possible initial states, e.g. the app is started, or specific data exists. For performance reasons, starting the app is usually done only in the first test case of a journey. Given.iStartMyApp();

  • Actions

    Define possible events triggered by a user, e.g. entering some text, clicking a button, navigating to another page. When.onTheWorklistPage.iPressOnMoreData();

  • Assertions

    Define possible verifications, e.g. do we have the correct amount of items displayed, does a label display the right data, is a list filled. At the end of the test case, the app is destroyed again. This is typically done only once in the last test case of the journey for performance reasons. Then.onTheWorklistPage.theTableShouldHaveAllEntries ().and.iTeardownMyAppFrame();

Please also note that you have to move the and.iTeardownMyAppFrame() concatenation from the previous opaTest function and put it at the end of the last test of a journey, in this case this is our new test. For performance reasons, we only start and destroy the app once per journey, as it takes several seconds to construct the iFrame and load the app. You can concatenate actions and assertions with the OPA helper object and in an easily readable way. The functions will be executed one after another.

Now you might wonder where all those descriptive functions and the helper object onTheWorklistPage are coming from. The answer is simple, the onTheWorklistPage object is a structuring element of OPA and inside we will implement the actions and assertions used in this test.

webapp/test/integration/pages/Worklist.js
sap.ui.require([
		'sap/ui/test/Opa5',
		'sap/ui/test/matchers/AggregationLengthEquals',
		'sap/ui/test/matchers/PropertyStrictEquals',
		'sap/ui/demo/bulletinboard/test/integration/pages/Common',
		'sap/ui/test/actions/Press'
	],
	function (Opa5, AggregationLengthEquals, PropertyStrictEquals, Common, Press) {
		"use strict";
		var sViewName = "Worklist",
			sTableId = "table";
		Opa5.createPageObjects({
			onTheWorklistPage: {
				baseClass: Common,
				actions: {
					iPressOnMoreData: function () {
						return this.waitFor({
							id: sTableId,
							viewName: sViewName,
							actions: new Press(),
							errorMessage: "The Table does not have a trigger"
						});
					}

				},
				assertions: {
					theTableShouldHaveAllEntries: function () {
						return this.waitFor({
							id: sTableId,
							viewName: sViewName,
							matchers:  new AggregationLengthEquals({
								name: "items",
								length: 23
							}),
							success: function () {
								Opa5.assert.ok(true, "The table has 23 items");
							},
							errorMessage: "Table does not have all entries."
						});
					},
					theTitleShouldDisplayTheTotalAmountOfItems: function () {
						return this.waitFor({
							id: "tableHeader",
							viewName: sViewName,
							matchers: function (oPage) {
								var sExpectedText = oPage.getModel("i18n").getResourceBundle().getText("worklistTableTitleCount", [23]);
								return new PropertyStrictEquals({
									name: "text",
									value: sExpectedText
								}).isMatching(oPage);
							},
							success: function () {
								Opa5.assert.ok(true, "The table header has 23 items");
							},
							errorMessage: "The Table's header does not container the number of items: 23"
						});
					}
				}
			}
		});
	});

As you can see, the OPA page object is constructed with the call Opa5.createPageObjects and a configuration object that contains the actions and assertions properties. It also inherits common functionality from a Common base object that is located in the same folder and contains helper functions for starting the app.

For our test case we need to add an action iPressOnMoreData and an existing assertion theTableShouldHaveAllEntries. OPA tests are running asynchronously, so each action and assertion starts with a waitFor statement. The OPA run time will check and wait for the condition to be fulfilled every 400 ms by polling. If the condition is met, the success function of the configuration is called. If the condition is still not fulfilled after a certain amount of time (by default it is 15 seconds but this can be configured) the test will fail.

Let’s start with the action iPressOnMoreData. We define a waitFor statement with the current view and the table. Those IDs are stored as internal variables in the require statement above and are available in all tests. OPA will now try to find the table based on IDs. As soon as the table is available on the screen and it can be interacted with (it is visible, not busy,...), the Press action is invoked, if not, the error message is displayed and the test fails. The Press action will simulate that a users chooses the More Data button. For some controls, a specific region has to be pressed. If you, for example, execute Press on a sap.m.Page, this will trigger the Back button's Press event. For more information, see the API Reference.

The assertion theTableShouldHaveAllEntries is structured similarly, but it does not trigger an action. Here, we use the success function of waitFor to assert if our application is in the expected state. This state is defined by the matchers (in our case we expect that the list contains 23 items by using the AggregationLengthEquals. The success function does not execute the additional checks that are needed for triggering an action. the liste does not have to be interactable to verify that the state of the application is correct..

With this helper object we can simply check the length of the table aggregation items to the expected number of items. We have 23 entries in our local mock data that we also use for this integration test. You can see that the number of items is actually hard-coded in the test. So only if the table has exactly 23 items, the matcher is evaluating to true and the assertion is passed successfully.
Note

The items in our app are served from the mock server with a slight delay so that we can see how a real service on a backend system would behave. Even if we would have a real backend, we would purposely use the mock server for manual testing and for using them in our test cases as the test data remains stable and unchanged. This creates a more reliable test environment and easier tests. So we can write a test that checks exactly for 23 items here.

Now run the webapp/test/integration/opaTests.qunit.html file and make sure that the test is failing. As you can see, the app is opened in a small iFrame on the lower right and the tests are running there. When our new test is invoked, OPA will run into a timeout because the trigger area is not found yet. You can see more information, if you open the developer console of your browser and check the messages in the console.
Conventions
  • Use OPA tests for UI-related integration tests

  • Structure OPA tests with page objects

  • Use the standard matchers provided by OPA5 if possible