@axway/api-builder-test-utils

2.2.1 • Public • Published

@axway/api-builder-test-utils

A set of utilities for testing projects and custom flow-nodes for API Builder.

Getting started

To get started with API Builder plugin development, see @axway/api-builder-sdk. The template for new flow-nodes includes both the SDK and test utils.

This module comes with a utility to help test your flow-nodes, MockRuntime.

const { MockRuntime } = require('@axway/api-builder-test-utils');

Your plugin exports the getPlugin function in index.js. This will be required in your unit-tests.

const getPlugin = require('../src');

The plugin will be loaded and invoked by the API Builder runtime, so it is necessary to use MockRuntime to emulate this for unit-testing.

const plugin = await MockRuntime.loadPlugin(getPlugin);

The runtime instance has a validate function that ensures the flow-node adheres to the schema.

it('should define valid flownodes', () => {
    // if this is invalid, it will throw and fail
    plugin.validate();
});

Then you can use getFlowNode to obtain a handle to your flow-node and invoke it with various arguments, checking the response as appropriate.

it('should succeed with valid argument', async () => {
    const flowNode = plugin.getFlowNode('myplugin');
    const params = {
        username: 'bob'
    };
    const { value, output } = await flowNode.getSomething(params);
    expect(value).to.deep.equal({ user: true });
    expect(output).to.equal('next');
});

In some cases, your flow-node may require credentials in addition to the standard parameters. They are treated as normal parameters and are passed along with the rest with the params as follows:

it('should succeed with expected arguments', async () => {
    const flowNode = runtime.getFlowNode('myplugin');
    const params = {
      username: 'bob',
      key: '1234' // The authorization parameter
    };
    const { value, output } = await flowNode.getSomething(params);
    expect(value).to.deep.equal({ user: true });
    expect(output).to.equal('next');
});

MockRuntime API

MockRuntime.loadPlugin(getPlugin, pluginConfig, options)

An async function that mocks the API Builder runtime and is used for testing API Builder plugins. Pass it the getPlugin function from index.js. It resolves to a plugin that is suitable for testing.

pluginConfig and options are values which are normally passed from API Builder. These values can be optionally provided from unit tests in order to test that your flow-node actions work with provided config, or when API Builder is configured in different ways.

const plugin = await MockRuntime.loadPlugin(getPlugin);

plugin.setOptions(options)

Sets options on the plugin loaded from MockRuntime.loadPlugin.

options.validateInputs - When enabled, when the action method is invoked with an input parameter, the MockRuntime will validate the value against the method's JSON schema and throw an Error if invalid. This ensures that the test's inputs adhere to the expected parameter JSON schema. The default is false. Note that this is a unit-test feature only to ensure that developers do not unintentionally test with invalid inputs. In your unit-tests, you will want to disable this input validation to verify that your action method can handle unexpected inputs during runtime. This is because flow-node inputs are not validated at runtime, so you still need to test that your flow-node handles invalid inputs.

options.validateOutputs - When enabled, when the action method triggers an output (e.g. "next") with a value, the MockRuntime will validate the output value against the output's JSON schema and throw an Error if invalid. This ensures that the responses from the action method adhere to the defined output JSON schema. The default is false. Note that this is a unit-test feature only to ensure that developers do not unintentionally test with invalid outputs.

plugin.validate()

Validates each flow-node within the plugin to ensure they adhere to the JSON schema.

plugin.getFlowNodeIds()

Returns the IDs, in alphabetical order, of each flow-node within the plugin.

plugin.getFlowNode(name)

Obtains a flow-node instance suitable for testing. Method actions are bound to the object instance as asynchronous functions. For example, if you have a method called getSomething:

const flowNode = plugin.getFlowNode('myplugin');
const result = await flowNode.getSomething({ username: 'jbloggs' });
result.value

The value that the action returned or resolved with.

If testing an action which has not been written using the API Builder SDK, this is the value of the second argument that was passed to the output callback.

cb.next(undefined, value);
result.output

The id of the output that was invoked. This will return undefined if the flow-node doesn't define any outputs.

result.callCount

The number of times the output callback was called. Not necesarry for testing plugins which use the SDK.

result.callback

The id of the output callback that was called.

If outputs.next() was called, you would expect the value to be next. If the default callback (i.e. outputs()) was called, this value will be undefined. Not necesarry for testing plugins which use the SDK.

MockLogger API

MockLogger.create()

A function which creates a new logger that mocks the API Builder logger. Pass it to any function which requires an API Builder logger.

const { MockLogger } = require('@axway/api-builder-test-utils');

The mock logger comes with all the functions mocked, allowing you to assert any log message. See simple-mock for more info about the way we mock these functions.

const logger = MockLogger.create();
logger.info('my log');
expect(logger.info.calls).to.have.length(1);
expect(logger.info.calls[0].args[0]).to.equal('my log');
const logger = MockLogger.create();

const kafkaLogger = logger.getScoped('kafka');
kafkaLogger.info('message recieved');

expect(logger.scopes.get('kafka').info.calls).to.have.length(1);
expect(logger.scopes.get('kafka').info.calls[0].args[0]).to.equal('message recieved');

Enable logging to console

MockLogger now supports logging to console. This enables you to see log messages from your flow-node, and can help when writing your unit-tests. You can enable the debug logging by setting the LOG_LEVEL environment variable when starting your tests:

LOG_LEVEL=info npm test

Accepted values are (in order of most-verbose to least) trace, debug, info, warn, error, fatal and none.

In the example above, we set the LOG_LEVEL to info. This means you would be able to see all logs at this level and above, meaning info, warn, error and fatal.

LOG_LEVEL only controls if the logs are output to console, and will not be impacted by manually setting the logger's logger.level().

Runtime API

The Runtime class is a utility that simplifies writing unit tests for API Builder projects. It abstracts the complexity starting/stopping the server and making client HTTP calls to it.

const { Runtime } = require('@axway/api-builder-test-utils');

Runtime(configOverrides)

Creates an instance of the Runtime that can be used for testing. The configOverrides are configuration options that explicitly override any default configuration options, or those defined in your application's configuration (i.e. the ./conf directory).

const runtime = new Runtime({ port: 9090 });
logger.debug('server bound to:', runtime.httpPort);

runtime.apiKey

Returns the configured API key for the runtime instance.

const runtime = new Runtime();
logger.debug('server test api-key:', runtime.apiKey);

runtime.httpPort

Returns the configured HTTP port for the runtime instance, e.g. 8080.

const runtime = new Runtime();
logger.debug('server bound to:', runtime.httpPort);

runtime.httpUrl

Returns the HTTP URL for the runtime instance, e.g. "http://localhost:8080".

const runtime = new Runtime();
logger.debug('server listening for requests on:', runtime.httpUrl);

async runtime.start()

A function which is used to start the API Builder server. The runtime.stop method also should be called after this function call. You only need to call this method if you are manually controlling the server start/stop. Otherwise, runtime.test is the recommended approach.

await runtime.start();

async runtime.stop()

Stops an instance of the server that was previously started with runtime.start. You only need to call this method if you are manually controlling the server start/stop. Otherwise, runtime.test is the recommended approach.

await runtime.start();
await runtime.stop();

async runtime.test(function)

Starts the runtime server instance and executes the supplied test function. This automatically handles calling runtime.start and runtime.stop, you do not need to call them. Any exceptions will be caught and the server instance will be stopped.

await runtime.test(() => {
  expect(runtime).to.exist;
});

async runtime.request(httpOptions)

Creates a HTTP client request to the server instance using the provided httpOptions. The runtime instance must first be started (see runtime.test). The httpOptions.path will be appended to the the full URL of the runtime with the configured HTTP port, e.g. "http://localhost:8080". Note that TLS is not yet supported.

Parameters

Below are the properties for httpOptions object.

Property Type Required Default Description
path string no A relative path to the HTTP resource.
method string no "GET" The HTTP method to use, one of: GET, PUT, POST, PATCH, HEAD, OPTIONS or DELETE
headers object no {} Map of headers to send to the server
body object, string, Buffer no Request body.

Returns

Returns a promise that resolves to the HTTP response.

Property Type Description
statusCode number The HTTP status code returned by the server.
headers object The HTTP headers returned by the server.
body * The optional HTTP body returned by the server.
Examples

Below are examples of how to use the runtime.request.

Example GET

Below is an example HTTP GET operation against a Bookstore API.

await runtime.test(async () => {
  const response = await runtime.request({
    url: '/api/bookstore/books?author=Austin'
  });
  expect(response.statusCode).to.equal(200);
  expect(response.body).to.deep.equal({ success: true });
});
Example POST JSON body and response

Below is an example HTTP POST operation against a Bookstore API that will send an receive JSON data.

await runtime.test(async () => {
  const response = await runtime.request({
    method: 'POST',
    url: '/api/bookstore/books',
    headers: {
      'accept': 'application/json',
      'content-type': 'application/json'
    }
    body: {
      title: 'Moby Dick',
      author: 'Herman Melville'
    }
  });
  expect(response.statusCode).to.equal(200);
  expect(response.body).to.deep.equal({ success: true });
});
Example form body

Below is an example HTTP POST operation against a Bookstore API that will send a HTTP form URL encoded body.

await runtime.test(async () => {
  const response = await runtime.request({
    method: 'POST',
    url: '/api/bookstore/books',
    headers: {
      'content-type': 'application/x-www-form-urlencoded'
    }
    body: {
      title: 'Moby Dick',
      author: 'Herman Melville'
    }
  });
  expect(response.statusCode).to.equal(201);
  expect(response.body).to.deep.equal({ success: true });
});
Example multipart body with file upload

Below is an example HTTP POST operation against a Bookstore API that will send a file as part of a HTTP multipart request.

await runtime.test(async () => {
  const response = await runtime.request({
    method: 'POST',
    url: '/api/bookstore/books',
    headers: {
      'content-type': 'multipart/form-data'
    }
    body: {
      title: 'Moby Dick',
      author: 'Herman Melville',
      image: {
        value: fs.createReadStream('teapot.png'),
        contentType: 'image/png'
      }
    }
  });
  expect(response.statusCode).to.equal(201);
  expect(response.body).to.deep.equal({ success: true });
});

Changes

2.2.1

  • #7600: Fix @axway/requester explicit dependencies.

2.2.0

  • #7599: Update @axway/requester.

2.1.0

  • #7595: Update @axway/requester dependency.

2.0.2

  • #7538: Fixed an issue with the request method in Runtime instance where the content-length header was calculated incorrectly when the request body was a string that contains multi-byte characters.

2.0.1

  • #7517: Lowered apibuilder.engines requirement to >= 4.0.0.

2.0.0

  • #6089: Breaking change: requires minimum Node.js version 16.x.

1.6.1

  • #7538: Fixed an issue with the request method in Runtime instance where the content-length header was calculated incorrectly when the request body was a string that contains multi-byte characters.

1.6.0

  • #7470: Added Runtime to support writing simpler unit tests.

1.5.11

  • #7466: Increase unit test coverage.

1.5.10

  • #7474: Internal dev-dependency move.

1.5.9

  • #7376: Bumped @axway/api-builder-sdk dependency.

1.5.8

  • #7434: Bumped @axway/api-builder-sdk dependency.

1.5.7

  • #7348: Fix bug where scoped loggers don't inherit the parent's log level.

1.5.6

  • #6933: Bumped @axway/api-builder-sdk dependency.

1.5.5

  • #7242: Bumped axway-schema dependency.

1.5.4

  • #7195: Bumped axway-schema dependency.

1.5.3

  • #7206: Internal clean up.

1.5.2

  • #7123: Bumped @axway/api-builder-sdk dependency.

1.5.1

  • #7057: Update documentation links.

1.5.0

  • #7066: The MockLogger now supports obtaining the current log level weight, e.g. by calling logger.level() with no arguments.
  • #7066: The MockLogger now supports programmatically changing level, e.g. by setting logger.level('INFO').
  • #7066: The MockLogger now supports logger.willLogAt('INFO'). It will return true if the configured log level is compatible with the provided upper-case log level.

1.4.0

  • #7010: The MockRuntime now supports input parameter validation when unit-testing and when the option validateInputs is enabled (see setOptions).
  • #7010: Improved an error message when a method was invoked with an unknown parameter.

1.3.0

  • #6999: The MockLogger can now log to console by providing LOG_LEVEL=<desired log level> when running your tests.

1.2.1

  • #6835: Bumped @axway/api-builder-sdk dependency.

1.2.0

  • #6786: Added support for scope and getScoped methods in MockLogger.
  • #6786: All MockLogger methods now come mocked with simple-mock.
  • #6786: Deprecated options argument to MockLogger.create(). This is no longer required to provide a stub function.

1.1.1 - 1.1.16

  • #6463: Bumped @axway/api-builder-sdk dependency.

1.1.0

  • #6461: Added setOptions to plugin that is returned from MockRuntime.loadPlugin, and an option validateOutputs that, when enabled, will validate output values against the output's JSON schema when the action method triggers an output. Defaults to false.

1.0.1

  • #6338: Bumped axway-flow-schema and @axway/api-builder-sdk.

1.0.0

  • #6441: No changes. First official release of @axway/api-builder-test-utils.

0.1.2

  • #6442: Fix bug in MockRuntime.loadPlugin's mockAction where parameters and authorizations were being provided as empty objects when API Builder would provide them as null.

0.1.1

  • #6437: Added getRawPlugin method to MockRuntime.loadPlugin. This returns the entire plugin that is provided to API Builder, and should be tested with caution.
  • #6437: Updated documentation.

0.1.0

  • #6337: Moved MockRuntime and MockLogger from @axway/api-builder-sdk
  • #6337: Added options.stub to MockLogger.create to make it easier to provide a custom stub for each log level.

Dependencies (6)

Dev Dependencies (0)

    Package Sidebar

    Install

    npm i @axway/api-builder-test-utils

    Weekly Downloads

    8

    Version

    2.2.1

    License

    SEE LICENCE IN LICENSE

    Unpacked Size

    51.2 kB

    Total Files

    9

    Last publish

    Collaborators

    • buildernpmuser
    • nkeranova
    • axway-npm
    • bladedancer
    • ddimonov-axway
    • neon-axway
    • vchauhan
    • mdimitrova
    • pdzhorev
    • axway_alasdair
    • pltod2
    • pbozhkovaxway
    • mbonchev-axway
    • axway-vertex