Join the #BuildWithBuildAgent Challenge! Get recognized, earn exclusive swag, and inspire the ServiceNow Community with what you can build using Build Agent.  Join the Challenge.

Mocking RESTMessageV2.execute in ATF Jasmine test causes JavaException

ab7289
Tera Contributor

I'm working on creating ATF tests to unit test some script includes in a scoped application. In the script include WebServiceUtil, I have a function that handles configuring and sending a RESTMessageV2 request.

 

The function being tested is as follows:

 

_rest: function(
        restMessage,
        restMethod,
        attrs = {},
        attrsNoEscape = {},
	rest
    ) {
		if (this.debug) {
            gs.info("Calling {0} {1}\nattrs: {2}\nattrsNoEscape:{3}",
                restMessage,
                restMethod,
                (!gs.nil(attrs) ? this.json.encode(attrs) : "{}"),
                (!gs.nil(attrsNoEscape) ? this.json.encode(attrsNoEscape) : "{}"));
        }

        if (this.debug) {
            var msg = "Calling :" +
                "\nrestMessage: " + restMessage +
                "\nrestMethod: " + restMethod +
                "\nParams: ";

            Object.keys(attrs).forEach(function(key) {
                msg += "\nkey: " + key + ", value: " + attrs[key];
            });
            msg += "\nParams No Escape: ";
            Object.keys(attrsNoEscape).forEach(function(key) {
                msg += "\nkey: " + key + ", value: " + attrsNoEscape[key];
            });
            gs.info(msg);
        }

        var requestBody,
            responseBody,
            response,
            status,
            jObj;

        try {
            // set string parameters
            Object.keys(attrs).forEach((k) =>
                !gs.nil(attrs[k]) ?
                rest.setStringParameter(k, attrs[k]) :
                null);
            // set non-escaped string parameters
            Object.keys(attrs).forEach((k) =>
                !gs.nil(attrsNoEscape[k]) ?
                rest
                .setStringParameterNoEscape(k, attrsNoEscape[k]) :
                null);

            // set the host name
            rest.setStringParameter("host",
                gs.getProperty("x_###_modtrak.host"));

            if (!gs.nil(this.mid)) {
                rest.setMIDServer(this.mid);
            }

			gs.info("pre-execute");
            response = rest.execute();
			gs.info("post-execute");
            responseBody = response.getBody();
			gs.info("post-getBody, pre-getStatusCode");
            status = response.getStatusCode();
			gs.info("got status code");
            jObj = this.json.decode(responseBody);
        } catch (ex) {
            var err = "There was an internal error when attempting to contact ";
            err += "The ModTrak REST Service: " + restMessage + ":" + restMethod;
            err += "\nError: " + ex;
            err += "\nError Exception: " + ex;
            if (!gs.nil(response)) {
                err += "\nREST Error Code: " + response.getErrorCode();
                err += "\nREST Error msg: " + response.getErrorMessage();
            }
            gs.error(err);
            responseBody = !gs.nil(ex) ? ex :
                response.getErrorMessage();
            return {
                error: true,
                errorMessage: responseBody
            };
        } finally {
            requestBody = rest ? rest.getRequestBody() : "";
        }

        if (this.debug) {
            gs.info("REST Message Response:" +
                "\nMessage: " + restMessage +
                "\nFunction: " + restMethod +
                "\nStatus: " + status +
                "\nRequestBody: " + requestBody +
                "\nResponseBody: " + responseBody);
        }

        return {
            error: (status != "200"),
            errorMessage: (status == "200" ? "" : responseBody),
            status: status,
            result: jObj
        };
    },

 

In the ATF Test, I want to mock out the rest.execute() method so that we are not actually making calls to external services. I have created the following ATF test to try it out:

 

(function(outputs, steps, params, stepResult, assertEqual) {
    describe("WebServiceUtil Script Include Tests", function() {
        it('Should return the ModTrak number if everything goes correctly', function() {

            var restMock = new sn_ws.RESTMessageV2(
                ModTrakAPI.CONSTANTS.API.CREATE.name,
                ModTrakAPI.CONSTANTS.API.CREATE.method
            );
            var mockResponse = {
                getBody: function() {
                    return "12345";
                },
                getStatusCode: function() {
                    return 200;
                }
            };
            spyOn(restMock, "execute").and.returnValue(mockResponse);
			
            var script = new x_###_modtrak.WebServiceUtil(null, true);

            var response = script._rest(
                ModTrakAPI.CONSTANTS.API.CREATE.name,
                ModTrakAPI.CONSTANTS.API.CREATE.method, {}, {},
                restMock
            );

            expect(restMock.execute).toHaveBeenCalled();
            expect(response.modtrak_number).toBe("12345");
        });
    });
})(outputs, steps, params, stepResult, assertEqual);

jasmine.getEnv().execute();

 

According to the SN Docs and Jasmine documentation, this implementation should not call the execute() method on RESTMessageV2, and instead return the mock response object I have configured, but instead when the test runs, when the rest.execute() method is called the following error is thrown:

 

Error: JavaException: java.lang.SecurityException: Method returned an object of type InterpretedFunction which is not allowed in scope x_###_modtrak

 

I have created a HI ticket for this but they are dismissing it as custom code. Has anyone had any luck trying to do the same?

2 REPLIES 2

Any idea what kind of permissions that would be? The code works when tested manually, the call is made and we get a response. It's just when I try to mock out the execute function the error appears.

ab7289
Tera Contributor

I was able to accomplish my goal by using a helper function to wrap the execute method. Updated code and tests as follows:

 

Script Include Code:

    rest: function(
        restMessage,
        restMethod,
        attrs = {},
        attrsNoEscape = {}
    ) {
        if (gs.nil(restMessage) || gs.nil(restMethod)) {
            gs.error("restMessage and restMethod are required");
            return {
                error: true,
                errorMessage: "restMessage and restMethod are required"
            };
        }
        if (this.debug) {
            gs.info("Calling {0} {1}\nattrs: {2}\nattrsNoEscape:{3}",
                restMessage,
                restMethod,
                (!gs.nil(attrs) ? this.json.encode(attrs) : "{}"),
                (!gs.nil(attrsNoEscape) ? this.json.encode(attrsNoEscape) : "{}"));
        }

        if (this.debug) {
            var msg = "Calling :" +
                "\nrestMessage: " + restMessage +
                "\nrestMethod: " + restMethod +
                "\nParams: ";

            Object.keys(attrs).forEach(function(key) {
                msg += "\nkey: " + key + ", value: " + attrs[key];
            });
            msg += "\nParams No Escape: ";
            Object.keys(attrsNoEscape).forEach(function(key) {
                msg += "\nkey: " + key + ", value: " + attrsNoEscape[key];
            });
            gs.info(msg);
        }

        var requestBody,
            responseBody,
            response,
            status,
            jObj;

        try {
            rest = new sn_ws.RESTMessageV2(restMessage, restMethod);

            // set string parameters
            Object.keys(attrs).forEach((k) =>
                !gs.nil(attrs[k]) ?
                rest.setStringParameter(k, attrs[k]) :
                null);
            // set non-escaped string parameters
            Object.keys(attrs).forEach((k) =>
                !gs.nil(attrsNoEscape[k]) ?
                rest
                .setStringParameterNoEscape(k, attrsNoEscape[k]) :
                null);

            // set the host name
            rest.setStringParameter("host",
                gs.getProperty("x_###_modtrak.host"));

            if (!gs.nil(this.mid)) {
                rest.setMIDServer(this.mid);
            }

            response = this._execute(rest);
            responseBody = response.getBody();
            status = response.getStatusCode();
            jObj = this.json.decode(responseBody);
        } catch (ex) {
            var err = "There was an internal error when attempting to contact ";
            err += "The ModTrak REST Service: " + restMessage + ":" + restMethod;
            err += "\nError: " + ex;
            err += "\nError Exception: " + ex;
            if (!gs.nil(response)) {
                err += "\nREST Error Code: " + response.getErrorCode();
                err += "\nREST Error msg: " + response.getErrorMessage();
            }
            gs.error(err);
            responseBody = !gs.nil(ex) ? ex :
                response.getErrorMessage();
            return {
                error: true,
                errorMessage: responseBody
            };
        } finally {
            requestBody = rest ? rest.getRequestBody() : "";
        }

        if (this.debug) {
            gs.info("Workday REST Message Response:" +
                "\nMessage: " + restMessage +
                "\nFunction: " + restMethod +
                "\nStatus: " + status +
                "\nRequestBody: " + requestBody +
                "\nResponseBody: " + responseBody);
        }

        return {
            error: (status != "200"),
            errorMessage: (status == "200" ? "" : responseBody),
            status: status,
            result: jObj
        };
    },

    /**
     * Internal method to enable unit testing
     */
    _execute: function(request) {
        return request.execute();
    },

 

Automated Test Scripts:

(function(outputs, steps, params, stepResult, assertEqual) {
    describe("WebServiceUtil Script Include Tests", function() {
        it('Should return the ModTrak number if everything goes correctly', function() {

            var mockResponse = {
                getBody: function() {
                    return "12345";
                },
                getStatusCode: function() {
                    return 200;
                }
            };

            var script = new x_###_modtrak.WebServiceUtil(null, true);

            var spy = spyOn(script, "_execute");
            spy.and.returnValue(mockResponse);

            var response = script.rest(
                ModTrakAPI.CONSTANTS.API.CREATE.name,
                ModTrakAPI.CONSTANTS.API.CREATE.method, {}, {}
            );

            expect(spy).toHaveBeenCalled();
            expect(response.result).toBe(12345);
            expect(response.error).toBe(false);
            expect(response.errorMessage).toBe("");
        });
 });

})(outputs, steps, params, stepResult, assertEqual);
// uncomment the next line to execute this script as a jasmine test
jasmine.getEnv().execute();