kinvey-business-logic-testing-library

Kinvey's Business Logic Testing Library

Usage no npm install needed!

<script type="module">
  import kinveyBusinessLogicTestingLibrary from 'https://cdn.skypack.dev/kinvey-business-logic-testing-library';
</script>

README

Helper module for testing Kinvey Business Logic

This module provides an easy way to connect to a Kinvey Business Logic (BL) instance running on docker. It is up to the user to download, configure and start the docker image itself before running code using this module. Two utility functions are provided to automate setting up the docker image.

The module serves two purposes:

  1. Integrate with the Kinvey BL CLI tool to support offline testing of BL code.
  2. Allow a developer to run a custom BL function in order to test its validity and behavior.

In order to use the module, you will first need to create and configure a client instance, and then call one of its API methods. These steps are explained in more detail below.

Table of contents

Usage

The module exposes the following API:

createMockRequest(fromJSON)

Create an instance of the request builder, which can be used to construct a request object to pass to the run... function described above. For more details, take a look at the request builder documentation below.

createMockResponse(fromJSON)

Create an instance of the response builder, which can be used to construct a response object to pass to the run... function described above. For more details, take a look at the response builder documentation below.

createClient(jsonConfiguration, callback)

Create a client instance, which will be able to communicate with the Business Logic docker container. The jsonConfiguration parameter allows you to optionally specify configuration options as a JSON object. The structure of the JSON object supports the following properties:

property name type description default value
blRootPath string the path to the root of the business-logic folder created by using the BL CLI tool N/A
environmentID string the ID of the environment to simulate 'BusinessLogicTest'
appSecret string the app secret of the simulated environment 'environmentID-app-secret'
masterSecret string the master secret of the simulated environment 'environmentID-master-secret'
containerHostOrIP string the hostname or IP address of the docker container running the BL instance $DOCKER_HOST or 'localhost'
runnerPort number the port exposed by the instance of the Kinvey BL running in the docker container 7000
proxyPort number the port exposed by the instance of the Kinvey proxy running in the docker container 2845

Note that you do not need to specify all values. Generally, you will want to configure the testing module with the blRootPath parameter. The environment-specific configuration parameters are provided as an option in case your BL code relies on this information. The other parameters, which are related to the docker container, will normally be obtained automatically (containerHostOrIP will default to the DOCKER_HOST environment variable, if defined, and to localhost otherwise; runnerPort and proxyPort will be obtained by inspecting the running docker container), and are only provided here as optional overrides.

The callback function should accept two arguments: an error (which will be set to null if no error has occurred), and an instance of the client.

The client instance created using this method exposes the following methods, which are described in detail below:

Example

var tester = require('business-logic-testing-library');

var options = {
  blRootPath: '/Users/JohnDoe/Documents/kinvey/business-logic',
  environmentID: 'MyAwesomeEnvironment'
}

tester.createClient(options, function(err, client) {
  if (err) {
    // handle error
  }

  client.runFunction( ... );
});

runCollectionHook(collectionName, blFunctionName, requestObject, responseObject, callback)

Run the code contained within a collection hook. This method relies on the existence of the collection hook .js file within the BL CLI folder structure. For example, if your collection is called MyCollection, and you are running an post-fetch hook, the tester will look for the code at _blRootPath_/collections/MyCollection/onPostFetch.js (where _blRootPath_ is the path to the root of the BL folder structure, as specified in the options to the createClient method).

Arguments
name type description
collectionName string the name of the collection associated with the hook
blFunctionName string the hook function to call. One of: onPreSave, onPostSave, onPreFetch, onPostFetch, onPreDelete, onPostDelete
requestObject JSON or mock-request-builder instance the object made available to the BL code through the request variable
responseObject JSON or mock-response-builder instance the object made available to the BL code through the response variable
callback function the function that will be called when the BL code has finished executing. This function should accept two parameters: error and blResult. The response will be a JSON object containing metadata, request and response properties.
Example
var requestObject = {
  body: {
    _id: 'abcd'
  },
  headers: {
    'x-kinvey-api-version': 3
  },
  username: 'foobar'
};

var responseObject = {};

client.runCollectionHook('MyCollection', 'onPreSave', requestObject, responseObject, function(error, blResult) {
  if (error) {
    // handle the error
  }
  else {
    console.log("Received response with body:", blResult.response.body);
  }
});

runCustomEndpoint(endpointName, requestObject, responseObject, callback)

Run the code contained within a custom endpoint. This method relies on the existence of the endpoint .js file within the BL CLI folder structure. For example, if your endpoint is called myEndpoint, the tester will look for the code at _blRootPath_/endpoints/myEndpoint.js (where _blRootPath_ is the path to the root of the BL folder structure, as specified in the options to the createClient method).

Arguments
name type description
endpointName string the name of the endpoint you wish to run
requestObject JSON or mock-request-builder instance the object made available to the BL code through the request variable
responseObject JSON or mock-response-builder instance the object made available to the BL code through the response variable
callback function the function that will be called when the BL code has finished executing. This function should accept two parameters: error and blResult. The response will be a JSON object containing metadata, request and response properties.
Example
var requestObject = {
  body: {
    _id: 'abcd'
  },
  headers: {
    'x-kinvey-api-version': 3
  },
  username: 'foobar'
};

var responseObject = {};

client.runCustomEndpoint('myEndpoint', requestObject, responseObject, function(error, blResult) {
  if (error) {
    // handle the error
  }
  else {
    console.log("Received response with body:", blResult.response.body);
  }
});

runFunction(codeToRun, requestObject, responseObject, callback)

Run code from a function or a function string. The function must match the custom endpoint signature: function onRequest(request, response, modules){ ... }.

Arguments
name type description
codeToRun function or string code you wish to run
requestObject JSON or mock-request-builder instance the object made available to the BL code through the request variable
responseObject JSON or mock-response-builder instance the object made available to the BL code through the response variable
callback function the function that will be called when the BL code has finished executing. This function should accept two parameters: error and blResult. The response will be a JSON object containing metadata, request and response properties.
Example
var requestObject = {
  body: {
    _id: 'abcd'
  },
  headers: {
    'x-kinvey-api-version': 3
  },
  username: 'foobar'
};

var responseObject = {};

var helloWorld = function onRequest(request, response, modules) {
  response.body = { 'hello': 'world' };
  response.complete();
};

client.runFunction(helloWorld, requestObject, responseObject, function(error, blResult) {
  if (error) {
    // handle the error
  }
  else {
    console.log("Received response with body:", blResult.response.body);
  }
});

dataStore

The testing container uses TingoDB to create a simulated data store and allow your Business Logic code to interact with data through the collectionAccess module, much like it normally would. While there are some differences between the API exposed by TingoDB and the one exposed by production instances of Kinvey (which use MongoDB), these are minor, and should allow you to simulate almost everything you wish. If you were to encounter a corner case which is handled differently by TingoDB and MongoDB, please first check the TingoDB documentation before contacting Kinvey support.

Please note that this data store exists entirely offline, within the docker container running on your local machine, and is not connected to Kinvey's production data store.

The data store exposes several methods, which can be accessed through the client's dataStore object. Detailed information on these methods and examples of their use can be found below.

importCollectionData(collectionName, jsonData, clearBeforeInsert, callback)

Allows you to import JSON data into a named collection.

The jsonData parameter can contain a JSON object or an array of JSON objects.

The clearBeforeInsert parameter, which defaults to false, will remove the contents of the collection before importing the data. This may be useful if you wish to be sure that your test environment starts with the same data every time.

The callback function should accept a single error argument, which will be set to null if no error has occurred.

Example

var tester = require('business-logic-testing-library');
var options = { ... };

tester.createClient(options, function(err, client) {
  if (err) {
    // handle error
  }

  var data = [{
      'first_name': 'John',
      'last_name': 'Doe'
    },
    {
      'first_name': 'Chuck',
      'last_name': 'Norris'
    }];

  client.dataStore.importCollectionData('customers', data, true, function(err) {
    if (err) {
      // handle error
    }

    ...
  });
});
removeCollectionData(collectionName, query, callback)

Allows you to remove entities from a named collection.

The query parameter should contain a MongoDB-style JSON query (for more details on constructing these types of queries, please check MongoDB's site).

The callback function should accept a single error argument, which will be set to null if no error has occurred.

Example

var tester = require('business-logic-testing-library');
var options = { ... };

tester.createClient(options, function(err, client) {
  if (err) {
    // handle error
  }

  client.dataStore.removeCollectionData('customers', { 'first_name': { '$in': ['John', 'Chuck'] } }, function(err) {
    if (err) {
      // handle error
    }

    ...
  });
});
getCollectionData(collectionName, query, callback)

Allows you to retrieve entities from a named collection.

The query parameter should contain a MongoDB-style JSON query (for more details on constructing these types of queries, please check MongoDB's site).

The callback function should accept an error argument, which will be set to null if no error has occurred, and a second argument which will contain the retrieved JSON data.

Example

var tester = require('business-logic-testing-library');
var options = { ... };

tester.createClient(options, function(err, client) {
  if (err) {
    // handle error
  }

  client.dataStore.getCollectionData('customers', { 'first_name': { '$in': ['John', 'Chuck'] } }, function(err, entities) {
    if (err) {
      // handle error
    }

    // at this point, the entities variable contains the results of execuring the query against the testing container's data store
  });
});

BL Modules

When testing, the email, logger, and push modules do not send out "real" e-mails or push notifications. Instead, the details of an e-mail, log, or push message are stored inside the following collections.

  • emails: _outgoingEmailMessages,
  • logs: _outgoingPushMessages,
  • push: _blLogs.

Retrieve these details as follows:

var tester = require('business-logic-testing-library');
var options = { ... };

tester.createClient(options, function(err, client) {
  if (err) {
    // handle error
  }

  client.runCustomEndpoint('myEndpoint', { }, { }, function(error, blResult) {
    if (err) {
      // handle error
    }

    client.dataStore.getCollectionData('_blLogs', { }, function(err, logs) {
      if (err) {
        // handle error
      }

      // at this point, the logs variable contains the logs recorded while executing the custom endpoint.
    });
  });
});

Common code

All methods support common code defined within the BL CLI folder structure. When any of the run... methods described above are called, the testing module will read the contents of any common code files contained within the _blRootPath_/common/ directory (where _blRootPath_ is the path to the root of the BL folder structure, as specified in the options to the createClient method). Any common code will be executed before running your collection hook/custom endpoint/function code.

Example
/**** contents of _blRootPath/common/helperFunctions.js ***/
var timesTwo = function(number) {
  return (number * 2);
}
/**** end of helperFunctions.js ****/


/**** contents of _blRootPath/custom/myEndpoint.js ***/
function onRequest(request, response, modules) {
  response.body = {
    multipliedNumber: timesTwo(parseInt(request.body.number))
  };
  response.complete();
};
/**** end of myEndpoint.js ***/


// testing code
var requestObject = {
  body: {
    number: 4
  }
};

client.runCustomEndpoint('myEndpoint', requestObject, {}, function(error, blResult) {
  if (error) {
    console.log("Error encountered! details:", error);
  }
  else {
    console.log("4 * 2 is", blResult.response.body.multipliedNumber);
  }
});

Helpers

Mock request builder

In order to run business logic using any of the run... methods listed on this page, you must pass in a request object. This object contains data and metadata about the (simulated) incoming HTTP request (FROM the client TO Kinvey), and is used to pass necessary information to the testing framework. In order to simplify the use of this object, you can use the mock-request-builder helper module, which exposes an API to create a request object.

Usage and constructor

To use the request builder, require the testing module, and then call its createMockRequest method. The constructor optionally accepts a JSON object containing initial values.

Example
var tester = require('business-logic-testing-library');
var options = { ... };

var requestObject = tester.createMockRequest({ body: { testing: true }});

tester.createClient(options, function(err, client) {
  if (err) {
    // handle error
  }

  client.runCustomEndpoint('myEndpoint', requestObject, {}, function(error, blResult) {});
});

Chaining

With the exception of toJSON, all methods of the request builder return the instance of the builder, allowing for chained method calls. For example:

var tester = require('business-logic-testing-library');
var requestObject = tester.createMockRequest();
requestObject.setBody({ testing: true }).addHeader('x-kinvey-api-version', 3);

setBody(jsonObject)

Set the body property of the request object, which corresponds to the body of the incoming HTTP request. Accepts a JSON object.

Example
var tester = require('business-logic-testing-library');
var requestObject = tester.createMockRequest();

requestObject.setBody({ testing: true });

client.runCustomEndpoint('myEndpoint', requestObject, {}, function(error, blResult) {});

setHeaders(jsonObject)

Set the headers property of the request object, which corresponds to the headers of the incoming HTTP request. Accepts a JSON object in which keys are header names and values are header contents.

Example
var requestObject = tester.createMockRequest();

requestObject.setHeaders({ 'x-kinvey-api-version': 3 });

client.runCustomEndpoint('myEndpoint', requestObject, {}, function(error, blResult) {});

addHeader(name, contents)

Add a header to the headers property of the request object, which corresponds to the headers of the incoming HTTP request. Accepts the name of a header, and its contents. If a header by that name already exists, this method will replace its contents.

Example
var requestObject = tester.createMockRequest();

requestObject.addHeader('x-kinvey-api-version', 3);

client.runCustomEndpoint('myEndpoint', requestObject, {}, function(error, blResult) {});

setParams(jsonObject)

Set the params property of the request object, which corresponds to the parameters of the incoming HTTP request. Accepts a JSON object in which keys are parameter names and values are their contents. For GET, PUT, and DELETE requests, you can use id (not: _id) as parameter name to specify an entity id. For POST, specify an _id in the request body.

Example
var requestObject = tester.createMockRequest();

requestObject.setParams({ 'query': { myField: 'myValue' }});

client.runCustomEndpoint('myEndpoint', requestObject, {}, function(error, blResult) {});

addParam(name, contents)

Add a parameter to the params property of the request object, which corresponds to the parameters of the incoming HTTP request. Accepts the name of a parameter, and its contents. If a parameter by that name already exists, this method will replace its contents.

Example
var requestObject = tester.createMockRequest();

requestObject.addParam('query', { myField: 'myValue' });

client.runCustomEndpoint('myEndpoint', requestObject, {}, function(error, blResult) {});

setAuthenticatedUsername(username)

Set the username of the authenticated Kinvey user making the simulated request. This is the username accessible to your business logic code by the modules.backendContext.getAuthenticatedUsername() method (for more details, please check our business logic reference).

Example
var requestObject = tester.createMockRequest();

requestObject.setAuthenticatedUsername('myUsername');

client.runCustomEndpoint('myEndpoint', requestObject, {}, function(error, blResult) {});

setTempObjectStore(jsonObject)

Set the value of the temporary object store available to your business logic code through modules.utils.tempObjectStore. For more details, please check our business logic reference.

Example
var requestObject = tester.createMockRequest();

var objectStore = {
  myProperty: 'myValue'
};

requestObject.setTempObjectStore(objectStore);

client.runCustomEndpoint('myEndpoint', requestObject, {}, function(error, blResult) {});

toJSON()

Returns the JSON object representing the request built by this helper.

Example
var tester = require('business-logic-testing-library');

var requestObject = tester.createMockRequest({ body: { testing: true }});

requestObject.setAuthenticatedUsername('myUser');

console.log(requestObject.toJSON());

/* outputs:
{
  body: {
    testing: true
  },
  username: 'myUser'
}
*/

Mock response builder

In order to run business logic using any of the run... methods listed on this page, you must pass in a response object. This object contains data and metadata about the (simulated) outgoing HTTP response (FROM Kinvey TO the client). In order to simplify the use of this object, you can use the mock-response-builder helper module, which exposes an API to create a response object.

Usage and constructor

To use the response builder, require the testing module, and then call its createMockResponse method. The constructor optionally accepts a JSON object containing initial values.

Example
var tester = require('business-logic-testing-library');
var options = { ... };

var responseObject = tester.createMockResponse({ body: { testing: true }});

tester.createClient(options, function(err, client) {
  if (err) {
    // handle error
  }

  client.runCustomEndpoint('myEndpoint', {}, responseObject, function(error, blResult) {});
});

Chaining

With the exception of toJSON, all methods of the response builder return the instance of the builder, allowing for chained method calls. For example:

var tester = require('business-logic-testing-library');
var responseObject = tester.createMockResponse();
responseObject.setBody({ testing: true }).setStatusCode(200);

setBody(jsonObject)

Set the body property of the response object, which corresponds to the body of the outgoing HTTP response. Accepts a JSON object.

Example
var responseObject = tester.createMockResponse();

responseObject.setBody({ testing: true });

client.runCustomEndpoint('myEndpoint', {}, responseObject, function(error, blResult) {});

setHeaders(jsonObject)

Set the headers property of the response object, which corresponds to the headers of the outgoing HTTP response. Accepts a JSON object in which keys are header names and values are header contents.

Example
var responseObject = tester.createMockResponse();

responseObject.setHeaders({ 'x-kinvey-api-version': 3 });

client.runCustomEndpoint('myEndpoint', {}, responseObject, function(error, blResult) {});

addHeader(name, contents)

Add a header to the headers property of the response object, which corresponds to the headers of the outgoing HTTP response. Accepts the name of a header, and its contents. If a header by that name already exists, this method will replace its contents.

Example
var responseObject = tester.createMockResponse();

responseObject.addHeader('x-kinvey-api-version', 3);

client.runCustomEndpoint('myEndpoint', {}, responseObject, function(error, blResult) {});

setStatusCode(code)

Set the status code of the response object, which corresponds to the HTTP status code of the outgoing HTTP response.

Example
var responseObject = tester.createMockResponse();

responseObject.setStatusCode(200);

client.runCustomEndpoint('myEndpoint', {}, responseObject, function(error, blResult) {});

toJSON()

Returns the JSON object representing the response built by this helper.

Example
var tester = require('business-logic-testing-library');

var responseObject = tester.createMockResponse({ body: { testing: true }});

responseObject.setStatusCode(200);

console.log(responseObject.toJSON());

/* outputs:
{
  body: {
    testing: true
  },
  status: 200
}
*/

Utilities

Setup

Start the required Docker images and return a tester instance. Prior to running this method, make sure Docker is installed properly. The set-up method expects a jsonConfiguration object as first parameter. The set-up method is best used in a before-hook of your test suite.

Example
// Assuming Mocha.
var tester = require('business-logic-testing-library');

var options = {
  blRootPath: '/Users/JohnDoe/Documents/kinvey/business-logic',
  environmentID: 'MyAwesomeEnvironment'
}

before(function(done) {
  tester.util.setup(options, function(err, client) {
    if(err) {
      // handle error
    }

    // client...
  });
});

Teardown

Currently, the teardown method is an empty method. This might change in the future.

Example
// Assuming Mocha.
var tester = require('business-logic-testing-library');

var options = {
  blRootPath: '/Users/JohnDoe/Documents/kinvey/business-logic',
  environmentID: 'MyAwesomeEnvironment'
}

before(...);

after(function(done) {
  tester.util.teardown(options, done);
});

License

Copyright 2016 Kinvey, Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.