The CreatorCon Call for Content is officially open! Get started here.

Unit test with RESTMessagev2

Che Pazzo
ServiceNow Employee
ServiceNow Employee

I have a function that uses sn_ws.RESTMessageV2 and I need to write a unit test for it.   Is there a way to Mock the sn_ws.REST classes for testing?

I can't seem to figure out how to properly unit test a function that uses it.

Here is my function:

MyClass.prototype = {

      sendRestMessage: function(method, uri, content) {

              var errorMsg;

              var request;

              var response;

              var responseBody;

              request = this._createRestMessage(method, uri, content);

              response = request.execute();

              responseBody = this._handleRestResponse(response);

              return responseBody;

      },

      /**

        * @returns {sn_ws.RESTMessageV2} A request object

        */

      _createRestMessage: function(method, uri, content) { ... },

      /**

        * @returns {String} The JSON String body of the response

        */

      _handleRestResponse: function(response) { ... },

};

Here is a bg script I tried to use to test it:

var json = new JSON();

json.prettify();

var expectedResult;

var actualResult = {};

var server = "fakeServer";

var method = "get";

var uri = "/fake/uri";

var content = { farts: "smell bad" };

var requestBody = '{"data":"My Fake Data"}';

var myc = new MyClass(device);

myc._createRestMessage = function(x_method, x_uri, x_content) {

      var request = new sn_ws.RESTMessageV2();

      actualResult['_createRestMessage'] = { method:x_method, uri:x_uri, content:x_content };

      request.execute = function() {

              return new sn_ws.RESTResponseV2();

      };  

      return request;

};  

myc._handleRestResponse = function(x_response) {

      return requestBody;

};  

actualResult['result'] = f5Ctrl.sendRestMessage(method, uri, content);

gs.print(json.encodeObject(actualResult));

I am hitting an error:

Evaluator: java.lang.IllegalStateException: Bad implementaion of call as constructor, name=RESTResponseV2

Which, I assume, means that I can't create a new RESTResponseV2().

thanks in advance.

1 ACCEPTED SOLUTION

Mike Setzer
Tera Expert

Well, I'm a few years late to the party, but I figured I'd put this here in case anybody else is trying to mock a rest message.

 

My solution was to create a class that could be extended to mock any specific REST message:

 

 

var X_MockRestMessage = Class.create();
X_MockRestMessage.prototype = Object.extendsObject(sn_ws.RESTMessageV2,{
    _responseBody: undefined,
    _responseCode: 200,
    restResponse: global.RESTResponse,
    _parentClass: sn_ws.RESTMessageV2,
    
    /*_________________________________________________________________
       * Description: Create dummy methods
       * Parameters: @Array methodNames - names of the methods
                     @Object fn (optional) - function to set the dummy properties equal too
       * Returns: @Abhijeet Singh
       ________________________________________________________________*/
    createDummyMethods: function(methodNames,fn){
        if(!fn){
            fn = function(){return;};
        }
        for(var i in methodNames){
            this[methodNames[i]] = fn;
        }
    },
    /*_________________________________________________________________
       * Description: method to override the execute method with when mocking data
                 eg.: this.execute = this.mockExecute;
       * Parameters: n/a
       * Returns: @Global.RESTECCResponse
       
       * future - a.  would be better if we could return @sn_ws.RESTResponseV2
                      the challenge being that it is a Java class
                  b.  would be better design to use an interface instead of a class               
       ________________________________________________________________*/
    mockExecute: function(){
        var _restResponse = Class.create();
        _restResponse.prototype = Object.extendsObject(global.RESTECCResponse);
        _restResponse.prototype._processEccQueueInput = function(){
            return;
        };
        
        var res = new _restResponse();
        res.body = this._responseBody;
        res.statusCode = this._responseCode;    
        return res;
    },
    /*_________________________________________________________________
       * Description: set the response body
       * Parameters: @String body
       * Returns: @Abhijeet Singh
       ________________________________________________________________*/
    setResponseBody: function(body){
        this._responseBody = body;
    },
    
    type: 'X_MockRestMessage'
});

 

 

Extend for specific REST message ("Some Service" in this case)

 

 

var X_MockSomeServiceRestMessage = Class.create();
X_MockSomeServiceRestMessage.prototype = Object.extendsObject(global.X_MockRestMessage,{  //extends sn_ws.RESTMessageV2
    initialize: function(restMessageName,method) {
        
        gs.info('X_MockSomeServiceRestMessage initializing');
        
        //if this is for method "Get Report" we will mock the results
        if(method == this.GET_REPORT){
            
            gs.info('X_MockSomeServiceRestMessage setting responseBody to system property x_mock_rest_response');
            
            //mock the response with the test date in the system property
            this.setResponseBody(gs.getProperty('x_mock_rest_response'));
            
            //override the execute method of sn_ws.RESTMessageV2 - 
            //call the mockExecute method of X_MockRestMessage instead
            this.execute = this.mockExecute;
        }
        
        this.createDummyMethods(this.dummyMethods);
    },
    //method of the Some Service Rest Message that we are mocking
    GET_REPORT: 'Get Report',
    dummyMethods: ['setStringParameter','setRequestHeader','setLogLevel','setQueryParameter'],
    
    //in case we need to call a parent method...
    _parentClass: global.X_MockRestMessage,
    type: 'X_MockSomeServiceRestMessage'
});

 

 
when calling it from another script include, we can set a class variable for the RESTMessageV2 object
 

 

var  X_MyClass = Class.create();
 X_MyClass.prototype = {
    initialize: function() {
        this.restMessage = gs.getProperty('x_mock_rest_message') == 'true' ?  
            X_MockSomeServiceRestMessage: 
            sn_ws.RESTMessageV2;
    },
    someMethod: function() {
        var myRestMessage = new this.restMessage('My REST API','My REST Method');
        //etc, etc
    },
    
    type: ' X_MyClass'
};

 

 
hope this helps.

View solution in original post

3 REPLIES 3

Chuck Tomasi
Tera Patron

Hi Mike,



I'm not certain I'm understanding all the details. What I can provide is how I test these.



The script include, somewhere does a call to instantiate a new sn_ws.RESTMessageV2() object, right? Whether you are using a defined outbound REST message (as an arg to the new call, or you are setting the endpoint, parameters, etc. explicity.)



That's how I construct my REST message calls inside a script include. To test it, I just write a small test script to call the method which contains the RESTMessageV2() call. If it works (e.g. creates a record) then I know I passed the test. Don't forget to also try bad cases/rainy day tests.


So, sure, I could have the function reach out to the target server and do something, then go clean it up when I'm done. I could also send a 'get' which won't actually change anything. However, I'm trying to avoid that, because if the target isn't available or there is some other issue with it, then this test (as would any other similar tests) would fail. I have integration tests that will test that part and want this unit test to isolate this function.



After I posted, I realized that when my function called execute(), it wasn't doing anything with the response other than handing it off to another function, so as long as I overloaded that function as well, it didn't really matter what it returned.



In the end, this did what I needed:


var fakeResponse = Class.create();


f5Ctrl._createRestMessage = function(x_method, x_uri, x_content) {


      var request = new sn_ws.RESTMessageV2();


      actualResult['_createRestMessage'] = { method:x_method, uri:x_uri, content:x_content };


      request.execute = function() {


              return fakeResponse;


      };


      return request;


};


Mike Setzer
Tera Expert

Well, I'm a few years late to the party, but I figured I'd put this here in case anybody else is trying to mock a rest message.

 

My solution was to create a class that could be extended to mock any specific REST message:

 

 

var X_MockRestMessage = Class.create();
X_MockRestMessage.prototype = Object.extendsObject(sn_ws.RESTMessageV2,{
    _responseBody: undefined,
    _responseCode: 200,
    restResponse: global.RESTResponse,
    _parentClass: sn_ws.RESTMessageV2,
    
    /*_________________________________________________________________
       * Description: Create dummy methods
       * Parameters: @Array methodNames - names of the methods
                     @Object fn (optional) - function to set the dummy properties equal too
       * Returns: @Abhijeet Singh
       ________________________________________________________________*/
    createDummyMethods: function(methodNames,fn){
        if(!fn){
            fn = function(){return;};
        }
        for(var i in methodNames){
            this[methodNames[i]] = fn;
        }
    },
    /*_________________________________________________________________
       * Description: method to override the execute method with when mocking data
                 eg.: this.execute = this.mockExecute;
       * Parameters: n/a
       * Returns: @Global.RESTECCResponse
       
       * future - a.  would be better if we could return @sn_ws.RESTResponseV2
                      the challenge being that it is a Java class
                  b.  would be better design to use an interface instead of a class               
       ________________________________________________________________*/
    mockExecute: function(){
        var _restResponse = Class.create();
        _restResponse.prototype = Object.extendsObject(global.RESTECCResponse);
        _restResponse.prototype._processEccQueueInput = function(){
            return;
        };
        
        var res = new _restResponse();
        res.body = this._responseBody;
        res.statusCode = this._responseCode;    
        return res;
    },
    /*_________________________________________________________________
       * Description: set the response body
       * Parameters: @String body
       * Returns: @Abhijeet Singh
       ________________________________________________________________*/
    setResponseBody: function(body){
        this._responseBody = body;
    },
    
    type: 'X_MockRestMessage'
});

 

 

Extend for specific REST message ("Some Service" in this case)

 

 

var X_MockSomeServiceRestMessage = Class.create();
X_MockSomeServiceRestMessage.prototype = Object.extendsObject(global.X_MockRestMessage,{  //extends sn_ws.RESTMessageV2
    initialize: function(restMessageName,method) {
        
        gs.info('X_MockSomeServiceRestMessage initializing');
        
        //if this is for method "Get Report" we will mock the results
        if(method == this.GET_REPORT){
            
            gs.info('X_MockSomeServiceRestMessage setting responseBody to system property x_mock_rest_response');
            
            //mock the response with the test date in the system property
            this.setResponseBody(gs.getProperty('x_mock_rest_response'));
            
            //override the execute method of sn_ws.RESTMessageV2 - 
            //call the mockExecute method of X_MockRestMessage instead
            this.execute = this.mockExecute;
        }
        
        this.createDummyMethods(this.dummyMethods);
    },
    //method of the Some Service Rest Message that we are mocking
    GET_REPORT: 'Get Report',
    dummyMethods: ['setStringParameter','setRequestHeader','setLogLevel','setQueryParameter'],
    
    //in case we need to call a parent method...
    _parentClass: global.X_MockRestMessage,
    type: 'X_MockSomeServiceRestMessage'
});

 

 
when calling it from another script include, we can set a class variable for the RESTMessageV2 object
 

 

var  X_MyClass = Class.create();
 X_MyClass.prototype = {
    initialize: function() {
        this.restMessage = gs.getProperty('x_mock_rest_message') == 'true' ?  
            X_MockSomeServiceRestMessage: 
            sn_ws.RESTMessageV2;
    },
    someMethod: function() {
        var myRestMessage = new this.restMessage('My REST API','My REST Method');
        //etc, etc
    },
    
    type: ' X_MyClass'
};

 

 
hope this helps.