How can I get a better testability of an SAPUI5 application? The essential precondition for efficient unit testing, a more adaptable application and better code is to master the handling of dependencies. For more background information about the general principles of Dependency Management, see a blog post by Martin Fowler: http://martinfowler.com/articles/injection.html
and for more information about Test Doubles, see a blog post by Gerard Meczaros:
http://xunitpatterns.com/Using%20Test%20Doubles.html.
In the following, four techniques are shown how to inject test doubles: View Constructor Injection, Controller Setter, Overwriting global Objects and Adapter.
First, the view constructor injection allows injecting an object into the view with the parameter viewData. This allows replacing objects, which are already needed before the controller is instantiated. This could, e.g., be the case for the resource bundle. In the following example, a Test Double of the Shell is created and injected using the view parameter viewData.
setup injects Test Double via viewData Parameter
var assignmentPage = sap.ui.view({
viewName : "ViewName" ,
type : sap.ui.core.mvc.ViewType.JS,
viewData : oTestDoubleFactory.createShellTestDouble()
});
Second, the controller setter allows injecting the object into the controller with a setter method.
setup injects TestDouble in Controller
this.oAlertActionAdapter = oTestDoubleFactory.createAlertActionAdapterTestDouble();
this.oController = assignmentPage.getController();
this.oController.setAlertActionAdapter(this.oAlertActionAdapter);
Third, a global object like OData can be overwritten. However, this technique is not recommended, because it could break other tests, which depend on the original state of the object. Therefore, the test should put the original object into a variable and afterwards revert to the original state of the object. But avoid this technique. You should avoid globale state and singletons as far as possible and design for good test isolation from the beginning. Otherwise it takes later a lot of effort to get the overall application testable.
setup saves original state and injects TestDouble
this.odata_request_original = OData; //remember original state
OData = oTestDoubleFactory.createODataTestDouble();
teardown reverts to original state
OData = this.odata_request_original; //revert to original state
Fourth, an alternative is the wrapper or adapter pattern combined with the parameter injection. This avoids the disadvantages of overwriting global objects. AlertActionAdapter, for instance, encapsulates all OData batch requests with regards to alerts. So the controller calls the AlertActionAdapter method assignAlerts instead of building and firing OData Batch Request. In the test, an instance of OData Test Double can be created and passed into the wrapper method assignAlerts with parameter injection.
setup create TestDouble
this.oModelTestDouble = oTestDoubleFactory.createODataModelTestDouble();
test function calls assignAlerts with TestDouble
this.oAlertActionAdapter.assignAlerts(this.oModelTestDouble,this.aAlerts,"MEI");
productive implementation of assignAlerts
this.assignAlerts =
function(oModel, aAlertIDs, sUserName, fnSuccess, fnError) {
varaBatchOperations = [];
for ( var i = 0; i < aAlertIDs.length; i++) {
aBatchOperations.push(oModel.createBatchOperation(
"/SetResponsiblePerson?AlertID='" + aAlertIDs[i].alertID +
"'&ResponsiblePersonID='" + sUserName + "'", 'GET' ));
}
oModel.addBatchReadOperations( aBatchOperations);
oModel.submitBatch( function(oData, oResponse, aErrorResponse) {
fnSuccess(aErrorResponse);
}, function(oError) {
fnError(oError);
}, false);
};