Link Search Menu Expand Document

Automated Testing

Automated testing is an essential part of the development workflow to ensure the technical correctness of the requirement. It is a recommended practise to guide the implementation through “Test-Driven Development” (TDD). To test against your acceptance criteria you should combine “TDD” with “Behaviour-Driven Development” (BDD).

How does “TDD” work?

TDD is defined by the red-green-refactor cycle:

  1. Write a new test that describes a part of the requirement and make sure that it fails (red)
  2. Write production code to let the test pass (green)
  3. Refactor both, test and production code, to clean it up (refactor)

If you are new to TDD it will help to do so-called “baby steps”. By this we mean that you write the simplest implementation possible. Then let the tests guide you to the next implementation iteration.

How does “BDD” fit in?

BDD uses the “Given-When-Then” pattern to test against the acceptance criteria. E. g.:

Given product is dish washer
 When product number is 123456
 Then the price should show 699€

The above text is then mapped to to test implementation, which executes your production code. This or a similar approach is commonly used for integration- and acceptance testing of you application.

How does “TDD” work in the context of SAPUI5?

If you generated you project structure with easy-ui5 you’ll find your test stubs within the test subdirectory. By default opa is used for integration tests. To run all tests run the test goal in package.json with npm

npm run test

Project test structure

Parent directories and files were omitted to focus on the test directory structure.

<project-dir>/uimodule/webapp/test
├── integration
│   ├── AllJourneys.js
│   ├── MainJourney.js
│   ├── /arrangements
│   │   └── Startup.js
│   ├── opaTests.qunit.html
│   ├── opaTests.qunit.js
│   └── /pages
│       └── Main.js
├── testsuite.qunit.html
├── testsuite.qunit.js
└── /unit
    ├── AllTests.js
    ├── /controller
    │   └── MainView.controller.js
    ├── /helper
    ├── /model
    ├── unitTests.qunit.html
    └── unitTests.qunit.js

Examples

The following example snippets are based on a new project generated by easy-ui5 and the testing-ui5-cart example project on GitHub (see references for link).

Test suite runner (test/testsuite.qunit.js):

window.suite = function () {
    "use strict";

    var oSuite = new parent.jsUnitTestSuite(),
        sContextPath = location.pathname.substring(0, location.pathname.lastIndexOf("/") + 1);

    oSuite.addTestPage(sContextPath + "unit/unitTests.qunit.html");
    oSuite.addTestPage(sContextPath + "integration/opaTests.qunit.html");

    return oSuite;
};

Unit Testing

QUnit Test (test/unit/MainView.controller.js):

sap.ui.define([
 "com/myorg/myUI5App/controller/MainView.controller"
], function(
 MainView
) {
 "use strict";

 QUnit.module("MainViewController", {
  beforeEach: function () {
   this.MainView = new MainView();
   sinon.stub(this.MainView, "returnFromCallMe", function () {
    return "stub";
   });
  },

  afterEach: function () {
      sinon.restore();
  }
 }, function () {
  QUnit.test("Should demonstrate how general stubs can be used", function (assert) {
   assert.strictEqual(this.MainView.callMe(), "stub", "Return value of callMe function should be from stub, but was from implmentation");
  });

    QUnit.test("Should demonstrate how inline stubs can be used", function (assert) {
      var stub = sinon.stub(this.MainView, "conditionalWithinAFunction", function () {
        return false;
      });
      assert.strictEqual(this.MainView.callMe(), 1);
      assert.strictEqual(stub.callCount, 1, "The conditionalWithinAFunction function has been successfully called");
    });
 });
});

Integration Test

OPA Test (test/integration/pages/Main.js):

sap.ui.require([
  "sap/ui/test/Opa5",
  "sap/ui/test/actions/Press"
], function (Opa5, Press) {
  "use strict";

  var sViewName = "com.myorg.myUI5App.view.MainView";

  Opa5.createPageObjects({
    onTheMainPage: {
      viewName: sViewName,

      actions: {
        // add action functions here
        iPressTheButton: function () {
          return this.waitFor({
            controlType: "sap.m.Button",
            actions: new Press(),
            errorMessage: "App does not have a button"
          });
        }
      },

      assertions: {
          // add assertion functions here
          iShouldSeeTheTitle: function () {
            return this.waitFor({
              controlType: "sap.m.Title",
              properties: {
                text: "com.myorg.myUI5App"
              },
              success: function() {
                Opa5.assert.ok(true, "The page shows the correct title");
              },
              errorMessage: "App does not show the expected title com.myorg.myUI5App"
            });
        }
      }

    }
  });

});

Implementation of the controller (<project-dir>/uimodule/webapp/controller/MainView.controller.js):

sap.ui.define(["com/myorg/myUI5App/controller/BaseController"], function (Controller) {
    "use strict";

    return Controller.extend("com.myorg.myUI5App.controller.MainView", {
      returnFromCallMe: function() {
        return 0;
      },
      conditionalWithinAFunction: function() {
        return true;
      },
      callMe: function() {
        return this.conditionalWithinAFunction() ? this.returnFromCallMe() : 1;
      }
    });
});

For further reading regarding this important topic on UI5 we recommend the Testing SAPUI5 Applications book (see references for the link).

References