Technical Articles
Testing UI5 apps, part 3.1: the Mockserver
Testing UI5 apps
- Part1: Setup and Unit Testing
- Part2: Integration aka OPA Testing
- Part3.1: Mockserver (this article)
- Part3.2: Code Coverage and other necessary Usefulities
- Part4: Advanced Testing mumbo-jambo
- Part5: Numbers, Experiences and Business Impact
After the first part of the series showed setup and Unit Tests and the second part covered Integration (aka “OPA”) Tests, we’ll broaden the scope a little and visit various topics sorrounding testing UI5 apps.
Given this article turned out to be longer than expected, I’m splitting “part3”: 3.1 covers the mockserver, 3.2 the remainig aspects.
(repeat hint from Blog Part 1: the code for this blog series along with installation instructions is located at https://github.com/vobujs/openui5-sample-app-testing – yep, by the URL you can already tell that I copied the official sample ToDo UI5 app and modified it to fit the purpose of this blog series about testing.)
TOC
Mockserver
One of the most underrated und neglected parts of UI5 is the excellent integrated mockserver. In fact, I’d postulate that -in a distributed development team- UI5’s mockserver is an ideal integration means between Frontend- and Backend-development. But don’t want to get ahead of myself and save this for the end of the post 🙂
init and start
In its standard use case, the mockserver takes an OData service’s metadata.xml file and auto-magically hooks into UI5’s OData calls. So to the UI5 app, it looks as if a “real” backend provides data.
var oMockServer = new sap.ui.core.util.MockServer({ // full namespace access for better readability
rootUri: "/my/odata/SRV" // e.g. see manifest.json: sap.app.dataSources.mainService.uri
})
oMockServer.simulate("sap/ui/demo/todo/test/metadata.xml"); // local metadata.xml
oMockServer.start();
If you were to start the mockserver in the application’s index.html
, all OData-requests will then be intercepted and answered by the mockserver, causing no more network activity.
sap.ui.getCore().attachInit(function() {
sap.ui.require([
"sap/ui/core/ComponentContainer",
"sap/ui/core/util/MockServer"
], function(ComponentContainer, MockServer) {
var oMockServer = new MockServer({
rootUri: "/my/odata/SRV"
})
oMockServer.simulate("/test/metadata.xml");
oMockServer.start();
new ComponentContainer({
name: "sap.ui.demo.todo",
settings: {
id: "todo"
},
manifest: true
}).placeAt("root");
});
});
on-demand access
But of course you want the mockserver to be switched on or off on demand – so for demonstration purposes, I’ve equipped the demo app with optional mockserver capabilites: by inserting sap-ui-mockserver
as URL GET parameter, the mockserver can be activated (and switches the entire application to using an OData-based model).
The sap.ui.core.util.MockServer
is extended as sap.ui.demo.todo.test.MockServer
and reads its’ configuration from manifest.json
directly.
The metadata.xml
is expected in /webapp/test/metadata.xml
.
Additional URL parameters are implemented for conveniently creating comfortable testing options:
sap-ui-mockserver-debug=true
enables verbose logging: for all HTTP access methods (GET, POST, PUT, DELETE, UPDATE), the request state is logged toconsole
sap-ui-mockserver-delay=<ms>
instructs the mockserver to wait<ms>
milliseconds before sending a response. Very useful to simulate network latency or slow backends!
Out of the box, the mockserver can generate type-matching data for Entites and Entity Sets automatically via
oMockServer.simulate(sMetadataUrl, {
bGenerateMissingMockData: true
})
While the resulting mockdata is convenient from a quantity-perspective, having static and/or more semantically meaningful data might be more desirable.
For better quality data, check out an extended version of the mockserver over at UI5 lab that generates more semantically valid mass data.
For having static data…
static mockdata, CR(UD)
…you need to do two things:
- provide an additional option for the mockserver at configuration-time:
oMockServer.simulate(sMetadataUrl, { sMockdataBaseUrl: "./test/mockdata", // dir holding static data bGenerateMissingMockData: true })
- provide content in a file
- named as
<entityset>.json
corresponding to the entity set name frommetadata.xml
,
located in thesMockdataBaseUrl
directory:
webapp/test/ └── mockdata └── todos.json
- in valid
JSON
format
[ { "guid": "67e2793d-f4d4-4d79-9fc8-f753b80728ba", "title": "Start this app with the mockserver", "completed": true }, { "guid": "67e2793d-f4d4-4d79-9fc8-f753b80728bb", "title": "Learn OpenUI5", "completed": false } ]
- named as
Static mockdata served by a simulated backend allows for reproducable results in Unit- and Integration Tests – but not only that, UI’s mockserver out of the box then supports all Create, Read, Update and Delete (CRUD
) on the static dataset!
Go ahead, try it: fire up the app via http://localhost:8080/?sap-ui-debug=true&sap-ui-mockserver=true&sap-ui-mockserver-debug=true, create a new ToDo item and watch the browser console
:
Caveat: the mockserver doesn’t support Edm.DateTime
keys in OData entities! Only possible workaround is to locally refactor them to be Edm.String
🙁
OPA test hookup
In order to hook up the mockserver to the Integration Tests, a two-step approach is necessary.
- detect whether the OPA tests are started with or without the mockserver
// in /webapp/test/integration/opaTests.qunit.html if (oUriParameters.get("sap-ui-mockserver")) { _global.mockserver = true; _global.mockserverParameters = oUriParameters; }
- firing up the mockserver so it suits the tests well.
Given the OPA tests for the openUI5-demotodo
are written so that the entire application is started and stopped in every test, we need to do the same with the mockserver:// in every webapp/test/integration/*Journey.js QUnit.module(<modulename>, { beforeEach: function () { if (_global.mockserver) { this.oMockserver = MockServer.init(_global.mockserverParameters); } }, afterEach: function () { if (this.oMockserver) { this.oMockserver.shutdown(); } } });
Detecting the mockserver usage on inital OPA suite startup is necessary due to some inner works of the OPA-QUnit bridge implementation: URL GET parameters are not forwarded to each *Journey.js
, so detecting them in QUnit.module-beforeEach
doesn’t work.
the metadata contract
Quick sum-up: UI5’s mockserver gives you
- a simulated backend, inlcuding latency
- extensive logging capabilites for OData operations in your app
- auto-generation of mockdata
- CRUD on static mockdata
- some features we haven’t even touched yet:
This should enable frontend development based on metadata.xml
only!
The OData service design is represented in that file and can be used at development time to create UI and UX against.
Thinking is through in a broader development context and timeline: why not design the OData service first, then give metadata.xml
as a “contract” to both frontend- and backend-development? “Frontend” can use UI5’s mockserver to simulate the backend, “Backend” knows what to implement based on the service design.
This allows both development streams to work in parallel rather than having backend/OData services done before developing frontend components – not only a huge time saving potential, but also enables agile adjustment of the OData design. If “Frontend” finds a cumbersome OData design, it’s easier to fix that via the metadata.xml
-contract than having to go through a “Backend” development cycle.
UI5’s mockserver has many capabilites! Only a brief intro in conjunction with Integration tests boosted the post to unforseen length – that’s why part3 of the “Testing UI5 apps” blog series is split in two; next up: automated testing, code coverage, and some useful utilities!
Great blog! Congrats.
I'd like to add 2 things...
Loading mock data for Entities
I've created a custom mockserver which loads meaningful data based on the metadata.xml. IMO, it's better than the generic "field name + index" and more efficient than creating JSON files manually. Have a look at the repo, you'll find more details and demos:
OpenUI5 Smart MockServer - https://github.com/mauriciolauffer/openui5-smart-mockserver
Starting mockserver before OPA tests
I'd start mockserver before starting the OPA tests (see example below, based on your OPA initialization) and I wouldn't kill it at every new test for the cost to stop/start is too high. Moreover, you don't want to turn mockserver off during an OPA test which communicates to the backend.