UI5 Testing - wridgeu/wridgeu.github.io GitHub Wiki

Introduction

Some examples are based on the official UI5 documentation walkthrough. Check out the following example application for a closer look.

Testing Pyramid

Component Tests

OPA5 - One Page Acceptance

OPA is a set of tools used to automate and validate UI (it comes with backend mocking). Tests are organized in Journesys, based on Pages to abstract the UI and make the tests more readable. It's designed to test one UI5 application (excluding backend testing).

Other automation frameworks like Selenium, Marionette or Puppeteer are framework agnostic, meaning one must rely on the generated HTML to code automation (IDs, CSS selectors...). OPA is designed by and for UI5 developers. It is capable of manipulating UI5 controls.

Overview of one OPA Test

Quite noticable here is the Give, When, Then pattern.

  • Arrangements (Given)

    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 (When)

    Define possible events triggered by a user, e.g. entering some text, clicking a button, navigating to another page.

    When.onTheWorklistPage.iPressOnMoreData();
  • Assertions (Then)

    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.iTeardownMyApp();

Example of one OPA Test

  • Pages

    Actual page objects that implement actions & assertions per part of the UI or in other words: An OPA5 Page object is used to group and reuse actions and assertions that are related to a specific part of the screen.

    Find an example implementation of a Page here or here.

    // Page scaffold (example impl.)
    sap.ui.define([
        "sap/ui/test/Opa5"
    ], function(Opa5){
        Opa5.createPageObjects({
            onAbstractPageName: {
                //baseClass: ClassOfferingCommonHelpers,
                actions: {
                    iExecuteAnAction: function(){ return this.waitFor(/*...*/); },
                    /*...*/
                },
                assertions: {
                    iCheckAnAssertion: function(){ return this.waitFor(/*...*/); },
                    /*...*/
                }
            }
        })
    });
  • Journeys

    A journey represents a user’s task in our app. A journey is composed of tests.

    • Each test follows the Given, When, Then pattern.

      • Given: setup the initial state of the test
      • When: execute actions
      • Then: check assertions

      Find an example implementation of a Journey here or here.

      // Journey scaffold (example impl.)
      sap.ui.define([
          "sap/ui/test/opaQunit"
      ], function(opaTest){
      
          //declare module
          QUnit.module("Journey name");
      
          //test1
          opaTest("Test name", function(Given, When, Then){
      
              Given.iStartMyapp();
      
              When.onAbstractPageName.iExecuteAnAction().and.iExecuteAnotherAction();
      
              Then.onAbstractPageName.iCheckAnAssertion().and.iTeardownTheApp();
      
          });
      
          //test2
          opaTest("Test name 2", function(Given, When, Then){
              /*...*/
          })
      })

Loading Pages & Journeys

Here we want to ensure that qUnit is loaded, we load the OPA as well as pages & journeys. Finally we start the Test.

Find an example implementation of a AllJourneys.js file here or here.

sap.ui.require([
    "sap/ui/test/Opa5",
    /* pages */
    /* journeys */
], function(Opa5){
    Opa5.extendConfig({
        /* Default settings */
    });

    //remove in CI/CD scenario
    QUnit.start();
});

The MockServer

The MockServer is a software component that captures AJAX requests and either answers them or lets them reach the backend. Check out the official documentation regarding the MockServer here.

Configuration
  • It requires the metadata to know the entities and relationships exposed by the ODATA Service.
  • It initializes the entity sets by generating them or loading JSON files.
_oMockServer = new MockServer({ rootUri: "/odata/EXAMPLE_SRV/" });

_oMockServer.simulate("model/metadata.xml", {
    sMockdataBaseUrl: "model/"
});

_oMockServer.start();
MockServer Entities

MockServer entities can be manipulated at any time.

var aExisting = _MockServer.getEntitySetData("SomeEntitySet");
var sGuid = "0MOCKSVRV-SOME-TEST-MOCK-GUID9999";

// Add a new entity
aExisting.push({
    "Guid": sGuid,
    "Title": "Generated",
    "__metadata": {
        id: "odata/EXAMPLE_SRV/SomeItemSet(guid'" + sGuid + "')",
        uri: "odata/EXAMPLE_SRV/SomeItemSet(guid'" + sGuid + "')",
        type: "EXAMPLE_SRV.SomeItem"
    }
});

_oMockServer.setEntitySetData("SomeItemset", aExisting);
MockServer Hooks

By default, the MockServer supports a lot of ODATA operations/hooks like: $batch, CRUD, Query parameters: paging, filtering, sorting, Single-Level Navigation-properties (function imports aren't supported).

  • Definition of a Hook:
    • method: GET, POST, PUT ...
    • path: a regexp matching the API URL
    • response function: behaviour implementation
var aRequests = _MockServer.getRequests();

aRequests.push({
    method: "POST",
    path: OData.entityNames.someItemSet,
    response: function(oXhr){
        //Initialize some values/fields
        var oBody = JSON.parse(oXhr.requestBody);
        oBody[OData.entityProperties.someItem.someAttribute] = null;
        oBody[OData.entityProperties.someItem.someotherAttribute] = null;
        oXhr.requestBody = JSON.stringify(oBody);

        return false; //Keep default processing
    }
});

_oMockServer.setRequests(aRequests);

Unit Tests

  • Given

    On the given object we can call arrangement functions like iStartMyAppInAFrame to load our app in a separate iFrame for integration testing.

  • When

    Contains custom actions that we can execute to get the application in a state where we can test the expected behavior.

  • Then

    Contains custom assertions that check a specific constellation in the application and the teardown function that removes our iFrame again.

Credits

⚠️ **GitHub.com Fallback** ⚠️