
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.

You can view and download all files in the Explored app in the Demo Kit under Testing - Step 6 .
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.
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.

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.
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.
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.
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'
],
function (Opa5, AggregationLengthEquals, PropertyStrictEquals, Common) {
"use strict";
var sViewName = "Worklist",
sTableId = "table";
Opa5.createPageObjects({
onTheWorklistPage: {
baseClass: Common,
actions: {
iPressOnMoreData: function () {
return this.waitFor({
id: sTableId,
viewName: sViewName,
matchers: function (oTable) {
return !!oTable.$("trigger").length;
},
success: function (oTable) {
oTable.$("trigger").trigger("tap");
},
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. A so-called matcher, another configuration object of OPA, is used for the evaluation of the test. The matcher must resolve to a Boolean value. If the matching condition is met, the success function is invoked, if not the error message is displayed and the test fails. The condition in the matcher checks for a DOM element called trigger inside the table. If this element is found, the success function will be invoked and we can simply trigger a jQuery tap event on it. With that our action is finished.
The assertion theTableShouldHaveAllEntries is structured similarly, but it does not trigger an action. In an assertion we only do checks in a waitFor statement and at least one check has to be successful to make the assertion pass. In this function, the matcher is not a custom one as in the action above but an AggregationLengthEquals matcher provided by OPA.
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.
Use OPA tests for UI-related integration tests
Structure OPA tests with page objects
Use the standard matchers provided by OPA5 if possible