Advanced test reactor
|
Use UI5 to test your ODATA service
Presented by Arnaud Buchholz
|
projects
A journey with OPA at UI5Con'18
recording training-ui5con18-opaOPA is a set of tools used to automate and validate UI and it comes with backend mocking.
Tests are organized in Journeys, based on Pages to provide a readable abstraction of the UI.
When the application has a backend counterpart, it can't run without being connected to it.
However, tests have to rely on a stable dataset and some features might be dangerous.
The MockServer is a software component that captures AJAX requests and either answer them or let them reach the backend.
It handles its own set of mocked data.
After UI5Con'18,
a colleague submitted a strange request.
His team needed a fake ODATA server to validate an unusual network configuration.
There must be a better way!
Why not running MockServer inside Node.js ?
jsdom simulates a browser inside Node.js.
const dom = new JSDOM(`<script id="sap-ui-bootstrap"
src="resources/sap-ui-core.js"
data-sap-ui-compatVersion="edge"
data-sap-ui-frameOptions='allow'
data-sap-ui-preload='async'>
</script>`, {
url: "https://example.org/",
referrer: "https://example.com/",
contentType: "text/html",
includeNodeLocations: true,
storageQuota: 10000000
});
https://github.com/jsdom/jsdom
The MockServer proof of concept demonstrated that it is possible to run UI5 in Node.js.
What else can be done?
Anything!
node-ui5 wraps UI5 API in Node.js.
require('node-ui5').then(({sap}) => {
sap.ui.require([
/* module dependencies */
], function (/* module values */) {
/* ... */
})
})
* Not supported by UI5
Native support of latest JavaScript features:
(node-ui5 provides asynchronous wrappers)
Easy to automate.
require('node-ui5')
.then(({ sap }) => {
sap.ui.require([
'sap/ui/model/odata/v2/ODataModel',
'node-ui5/promisify'
], async function (ODataModel) {
console.log('Creating ODataModel...')
const model = new ODataModel({
serviceUrl: 'http://localhost:8080/odata/TODO_SRV'
})
console.log('Reading $metadata')
await model.metadataLoaded()
console.log('Reading TODO items')
const { results } = await model.readAsync('/TodoItemSet')
console.log(`Found ${results.length} todo items`)
/* ... */
UI developers are the primary users of the ODATA service but they are not involved in ODATA testing.
Furthermore, testing usually focuses on CRUD methods and rarely on checking $metadata.
ODATA calls are made through the ODataModel class.
When building an application, a good practice is to also prepare the MockServer for OPA testing.
The MockServer can be implemented the TDD way.
To some extend, the same tests may apply to a real server.
OPA is used for integration testing but with no connection to the server.
That's why the MockServer is part of the framework.Thanks to OPA concepts, the developer can automate the application.
But the application must be hosted on the server to access the ODATA service. However, the test code is not delivered.
It is not possible to run OPA tests against a distant server.
node-ui5 offers a configurable proxy server that flattens the system landscape.
The OPA code can access the distant server as if it was local.
https://github.com/ArnaudBuchholz/training-ui5con18-opa.git
npm i in the cloned / unzipped foldernpm run-script ui5con19https://github.com/ArnaudBuchholz/training-ui5con18-opa.git
ui5con19 branch
|
|
The goal of this workshop is to pass all the tests.
webapp/test/unit/test/MockServer.jshttp://localhost:8080/test/unit/unitTests.qunit.html?module=MockServer/training-ui5con18-opa/webapp/test/unit/unitTests.qunit.htmlTo achieve this goal, the initial MockServer implementation must be customized.
webapp/test/MockServer.jshttp://localhost:8080/index.html/training-ui5con18-opa/webapp/index.htmlMockServer is capable of preloading
entities from a JSON file.
"/Date(" + date.getTime() + ")/"
Solution in
/training-ui5con18-opa/workshop/ui5con19-mockserver/step1/webapp/model/TodoItemSet.json
Using getEntitySetData and setEntitySetData, one can manipulate the MockServer's entities.
__metadataoItem.__metadata = {
uri: "/odata/TODO_SRV/TodoItemSet(guid'" + sGuid + "')",
type: "TODO_SRV.TodoItem"
};
Allocate new entities in a loop with ./newItem
sap.ui.define([
"sap/ui/core/util/MockServer",
"sap/ui/demo/todo/const",
"./newItem"
], function(MockServer, CONST, getNewItem) {
/* ... */
var todoItemSet = CONST.OData.entityNames.todoItemSet;
var aTodoItemSet = _oMockServer.getEntitySetData(todoItemSet),
aTodoItemSet.push(getNewItem("title",
/* due date */ new Date(),
/* completion date, optional */ new Date()
));
_oMockServer.setEntitySetData(todoItemSet, aTodoItemSet);
Solution in
/training-ui5con18-opa/workshop/ui5con19-mockserver/step2/webapp/test/MockServer.js
MockServer offers default CRUD implementations.
Using getRequests
and setRequests,
it is possible to alter their behavior.
{
method: "MERGE",
path: CONST.OData.entityNames.todoItemSet + "\\(guid'([^']+)'\\)",
response: function(oXhr, sCapturingGroupValue) {
// oXhr.respond(iStatusCode, mHeaders, sBody);
return true || false;
}
}
method is the HTTP verb to matchpath is a regular expression
matching the request URL (string or RegExp)
response functionoXhr simulates an XMLHttpRequest object
oxhr.respond to answer the requestresponse function returns true,
the MockServer won't search for another matching definition (LIFO)var aRequests = _oMockServer.getRequests();
aRequests.push({
method: "?",
path: CONST.OData.entityNames.todoItemSet + "\\(guid'([^']+)'\\)",
response: function(oXhr, sTodoItemGuid) {
var oBody = JSON.parse(oXhr.requestBody);
if (oBody[CONST.OData.entityProperties.todoItem.completed]) {
} else {
}
oXhr.requestBody = JSON.stringify(oBody);
return false; // Keep default processing
}
});
_oMockServer.setRequests(aRequests);
Solution in
/training-ui5con18-opa/workshop/ui5con19-mockserver/step3/webapp/test/MockServer.js
/training-ui5con18-opa/workshop/ui5con19-mockserver/step4/webapp/test/MockServer.js
It is important to check how an application
behaves in case of errors.
if (sTodoItemGuid === STOP_PROCRASTINATING_GUID) {
oXhr.respond(400, {
"Content-Type": "text/plain;charset=utf-8"
}, "I'll start tomorrow !");
return true; // Skip default processing
}
Solution in
/training-ui5con18-opa/workshop/ui5con19-mockserver/step5/webapp/test/MockServer.js
oXhr.respond(200, {
"Content-Type": "application/json;charset=utf-8"
}, JSON.stringify({
d: oResult
}));
Solution in
/training-ui5con18-opa/workshop/ui5con19-mockserver/step6/webapp/test/MockServer.js