SAP / openui5

OpenUI5 lets you build enterprise-ready web applications, responsive to all devices, running on almost any browser of your choice.
http://openui5.org
Apache License 2.0
2.9k stars 1.23k forks source link

How to mock module in qunit test #4066

Closed coderjinhui closed 3 weeks ago

coderjinhui commented 3 weeks ago

OpenUI5 version: latest

I have question on how to mock the import module in UT.

For example, I have 2 file:

in Page1.controller.js, I import the getShell.js to use some public functions.

sap.ui.define([
    "app/me/cards/CardComposite",
    "app/me/utils/getShell"
], function(CardComposite, getShell) {
    "use strict";

    const Page1 = CardComposite.extend("app.me.cards.Page1", {
        metadata: {
            properties: {
                growing: {type: "boolean", defaultValue: true, group: "Designtime"}
            }
        },
    });

    Page1.prototype.init = function() {
        CardComposite.prototype.init.apply(this, arguments);
        const shell = getShell();
        // do some thing
    };

    return Page1;
}, true);

in getShell.js, the getShell function will get the shell component

sap.ui.define([], function() {
    "use strict";

    const getShell = function() {
        return xxx
    }

    return getShell;
}, true);

How can I mock the imported getShell? So that I can ignore the real logic when this function will be called in UT.

heimwege commented 3 weeks ago

We are using sinon stubs for that (Details see https://sinonjs.org/releases/latest/stubs/)

flovogt commented 3 weeks ago

Please have a look at https://ui5.sap.com/#/topic/457eaada68a24187858fd5e8b21a4892 "Stubs" section.

ThomasChadzelek commented 3 weeks ago

We are using sinon stubs for that (Details see https://sinonjs.org/releases/latest/stubs/)

@heimwege Can you please provide a concrete example for the use case shown above? I was having difficulties in such cases.

flovogt commented 3 weeks ago

@coderjinhui If you wanna mock the whole module, you could configure a loadconfig. Loader config sample can be seen here https://github.com/flovogt/ui5-external-library.

heimwege commented 3 weeks ago

@ThomasChadzelek sure, here we go:

import * as Sinon from "sinon";
import Fragment from "sap/ui/core/Fragment";

[...]

QUnit.test("Check something loading a fragment", async assert => {
    const fragmentStub = sinon.stub(Fragment, "load"); //define what should be stubbed
    fragmentStub.returns(Promise.resolve("hello world")); //define return value of stub

    //test something here that actually calls Fragment.load but will get the return value from fragmentStub instead

    fragmentStub.reset(); //don't forget to reset the stub to not interfere other tests
});

Above code is TypeScript so not sure to what it will be transpiled in the end. Hope that helps.

ThomasChadzelek commented 3 weeks ago

@heimwege Thanks a lot, but doesn't that only work for modules which return an object? Only then you can stub a method there. If the module import itself already gives you a function, I do not see how to stub that.

P.S.: That the reason for constructs like https://github.com/SAP/openui5/blob/3ca1b317d86acadf5a45ced6fc209318ed177a3d/src/sap.ui.core/src/sap/ui/model/odata/v4/lib/_Helper.js#L853

codeworrior commented 3 weeks ago

@ThomasChadzelek and @flovogt made two important points:

  1. Out of the box, it's not possible to use sinon to stub or spy the default export of a UI5 AMD module. Sinon requires an object property for that purpose.
  2. But in certain cases, one can use functionality of the ui5loader and its APIs to create substitute modules that can be mocked.

I've created a complete POC here (internal Snippet 707744) that shows how to stub sap/m/Button.

But be aware: this is not a functionality that UI5 provides. It's just a sketch how it could be done.

And, last but not least, there are many limitations (you'll find some in the comments in the sample, but I'm sure there are more).

coderjinhui commented 3 weeks ago

@ThomasChadzelek and @flovogt made two important points:

  1. Out of the box, it's not possible to use sinon to stub or spy the default export of a UI5 AMD module. Sinon requires an object property for that purpose.
  2. But in certain cases, one can use functionality of the ui5loader and its APIs to create substitute modules that can be mocked.

I've created a complete POC here (internal Snippet 707744) that shows how to stub sap/m/Button.

But be aware: this is not a functionality that UI5 provides. It's just a sketch how it could be done.

And, last but not least, there are many limitations (you'll find some in the comments in the sample, but I'm sure there are more).

I tried, but found another problem when using ui5loader. When I set the loader.config in A.test.js, B.test.js will also use the mock module.

Is there any way to restore the config in B.test.js?

flovogt commented 3 weeks ago

The loader config is a "singleton". So changing it in one file means changing it for all files whose are proceeded after configuration is changed. There is no restore API available, you would have to re-configure the loader after the test is finished but keep in mind, if you use async loading it might be that B.test.js is already requested before the loader config changed back. As @codeworrior already said above, UI5 does not provide official support for this scenario.