Get unlimited public & private packages + team-based management with npm Teams.Learn more »

@cepharum/mockup-service

0.1.5 • Public • Published

mockup-service

License

MIT

About

This module is designed to help with mocking network services some code to be tested relies on. It supports an extensible domain-specific language describing an ordered list of rules with each rule containing a test applied on incoming requests and a response to be sent back in case of test succeeds.

Install

npm install -D @cepharum/mockup-service

Usage

Consider the following definition of rules for an HTTP service:

headers.content-type = text/plain ==>
    Hello tester!
EOT

headers.content-type = text/html AND method = POST ==>
    Hello posting HTML-tester!
EOT

headers.content-type ~ /image\/jpe?g/ ==>
    Hello JPEG-tester!
EOT

/just/some/path ==>
    Hello path-tester!
EOT

/some/interpolated/response ==>
    Hello {{headers.x-value}}-tester!
EOT

/api/:prefix/test ==>
    Hello {{rule.params.prefix}}-tester!
EOT

/some/actual/message AND query.type === "actual" ==> MSG
    set-cookie: mycookie=value:set
    x-folded-value: so
        me
      folded value

    Hello {{query.type}} message-tester!
EOM

In a unit test this definition could be used like this:

"use strict";
 
const { describe, before, after } = require( "mocha" );
const { HttpService } = require( "@cepharum/mockup-service" );
 
const Mock = new HttpService( `
headers.content-type = text/plain ==>
    Hello tester!
EOT
 
headers.content-type = text/html AND method = POST ==>
    Hello posting HTML-tester!
EOT
 
headers.content-type ~ /image\/jpe?g/ ==>
    Hello JPEG-tester!
EOT
 
/just/some/path ==>
    Hello path-tester!
EOT
 
/some/interpolated/response ==>
    Hello {{headers.x-value}}-tester!
EOT
 
/api/:prefix/test ==>
    Hello {{rule.params.prefix}}-tester!
EOT
 
/some/actual/message AND query.type === "actual" ==> MSG
    set-cookie: mycookie=value:set
    x-folded-value: so
        me
      folded value
 
    Hello {{query.type}} message-tester!
EOM
` );
 
describe( "Mock-up service for HTTP", () => {
    before( () => Mock.start() );
    after( () => Mock.stop() );
 
    it( "responds ...", () => {
        // return require( "node-fetch" )( Mock.url ).then( response => {} );
    } );
 
    // TODO: add more tests here
} );

Using Mock.url in actual tests to be added the mocked service's URL is available for use with your particular client library to be tested. Instead of using Mock.url you might also use Mock.address to fetch the IP address service is listening on and Mock.port to get the related port service is listening on.

The service mock-up also includes a client Mock.query() for querying the service but this mostly for testing the implementation of mock-up service itself.

API

The API basically consists of services to be instantiated in combination with a script describing how the resulting service is meant to respond to either incoming request.

As demonstrated before, in case of HTTP service this looks like this:

const { HttpService } = require( "@cepharum/mockup-service" );
const Mock = new HttpService( scriptDefiningRules );

Methods

Every such instance of a service provides the following methods:

Mock.start() : Promise<{address, port}>

Starts service for processing defined rules on every incoming request. The method promises information on then fully started service. This information is promised as object containing running service's IP address in property address and its port in property port.

Mock.stop() : Promise

Shuts down previously started service. Promises service shut down eventually.

Mock.query() : Promise

This method is provided to query the started service e.g. for testing whether some defined response is actually delivered. This is used a lot in testing this library. The method's signature stringly depends on type of service:

HttpService: Mock.query( method, url [, headers [, body] ] ) : Promise

This version accepts desired HTTP method and URL for request. In addition, an object listing request headers and some request payload might be given.

Properties

Mock.url

This property is exposing the service's URL. Its content strongly depends on type of service. This information is reliable after having started service, only.

Mock.address

This property is exposing the address the service is listening on. This information is reliable after having started service, only.

Mock.remoteAddress

This property is exposing the address for accessing the service from a client's point of view. This information is reliable after having started service, only.

Mock.port

This property is exposing the port the service is listening on. This information is reliable after having started service, only.

Events

Either service instance is deriving from EventEmitter as provided by Node and thus it is possible to manage listeners for selected events to be emitted by either service.

request

The request-event is emitted prior to searching defined rules for the earliest one matching incoming request. Any listener is invoked with these arguments:

  1. descriptor of incoming request
  2. manager for controlling response

response

The response-event is emitted after having sent response to client. Any listener is invoked with these arguments:

  1. descriptor of incoming request
  2. manager for controlling response
  3. an array of Buffer instances sent in response
  4. descriptor of matched rule which delivered the response

error

The error-event is emitted when handling any request resulted in error. This might happen once per received request. Any listener is invoked with a description of error as instance of Error as sole argument.

Syntax

The script for mocking-up a service is a sequence of rules processed from top to bottom. Every rule defines a test to be performed on any incoming request and a response to send back in case of a matching test.

<test> ==> <response>

Several kinds of tests are supported:

Tests

Custom Comparison Tests

Custom comparison tests consist of a source's name exposing some actual value, a comparing operator and a literal value to compare read source with.

<source> <operator> <value>

The source may consist of several segments separated by full stop with each segment selecting another level of previously addressed information while descending into a hierarchy of information. The initial source is description of current request. This description may be include different additional information depending on selected type of mock-up service.

The operator is one out of these options:

Operator Comparison
= value equals source
== value equals source
!= value is different from source
<> value is different from source
<> value is different from source
=== value equals source and is of same type
eq value equals source and is of same type
!== value is different from source or differs from source by type
neq value is different from source or differs from source by type
ne value is different from source or differs from source by type
< value is less than source
lt value is less than source
<= value is less than or equal source
lte value is less than or equal source
> value is greater than source
gt value is greater than source
>= value is greater than or equal source
gte value is greater than or equal source
~ value is a regular expression that matches source
!~ value is a regular expression that doesn't match source

:::tip Example path == /some/path is testing path name of URL in context of an HTTP service. In context of same service query.arg = 1 tests whether URL query parameter named arg has value 1 or not. :::

See the following table for a list of probable sources:

Name Description
method verb of HTTP request selecting request method
httpVersion HTTP version selected by request
headers.* HTTP request headers - Replace * with name of header to read. Headers are provided with all letters converted to lowercase variants.
url full request URL consisting of path and query
path leading part of request URL up to first ?
query.* query parameter extracted from part of request URL following first contained ? - Replace * with name of parameter to read.
params.* parameter extracted from pathname of request URL (in opposition to parameters found in its query) - Works after using convenient path test as described below, only. Replace * with name of parameter to read
body.* parameter extracted from transmitted body - Works if body is encoded as JSON and header "content-type" is set. Replace * with name of parameter to read.

Conveniently Test Path

Tests starting with a forward slash are considered to provide a path name pattern to be matched by requests. The provided pattern is passed through path-to-regexp to generate a pattern that's eventually used to test an incoming request. See description of supported syntax there.

The pattern may include parameters to be exposed in request.params. This enables use of those parameters when defining interpolated responses.

:::tip Example /some/:myname* is testing path name of URL in context of an HTTP service. Matching path names are /some, /some/sub, /some/deep/arbitrary etc. with exposing part following /some/ as params.myname e.g. for response interpolation. :::

Combining Tests

Multiple tests may be combined per rule using either keyword AND or OR between two adjacent test definitions. Using either keyword result in an intersection or union of combined tests. Multiple tests may be combined this way, but only one of the keywords may be used throughout the combination of tests in a single rule.

:::tip Examples Intersection: <test-a> AND <test-b> or <test-a> AND <test-b> AND <test-c> Union: <test-a> OR <test-b> or <test-a> OR <test-b> OR <test-c> :::

Responses

In a rule's definition response is separated from preceding tests by ==>. This arrow must be written literally. Additional meta information on response might be given after that arrow, but in same line.

Actual response starts after arrow and some optional meta information. Either kind of response may use custom marker for end of response, but currently all supported response types use EOT written in a separate line at least.

Delaying Responses

Responses may be delayed by intention. A delay is defined by writing number of milliseconds followed by another arrow ==> right after the arrow marking end of tests as described before.

<test> ==> 1500 ==> <response>

This results in response being sent back to client in about 1500ms.

:::warning Due to Javascript lacking realtime capabilities either time may be considered estimate value of delay, only. :::

By defining a range using two positive integer values separated by a single dash the actual delay per response is picked randomly from this defined range.

<test> ==> 1500-5000 ==> <response>

This results in response being sent back to client with an arbitrary delay between 1500ms and 5000ms.

/just/some/path ==> 5000 ==>
    Hello path-tester!
EOT

Selecting Type of Response

The type of response is selected using special type name right after ==>. In case of declaring a delay this applies to the second ==>. Following type names are supported:

Type Name Resulting Type of Response
TEXT Simple Text
MSG RFC-2822-Like Message
MESSAGE RFC-2822-Like Message

When omitting this information TEXT is used by default.

:::warning Note: This information selects one out of several supported response handlers. It doesn't define the kind of actual response but the way it is defined. Using TEXT might be sufficient to sent arbitrary data, but MSG is required to be actually able to define response headers. So, sending plain text responses with custom headers still requires use of MSG response type. :::

Simple Text Responses

Starting with line succeeding the one containing ==> with optionally given meta information and type selector the actual response is provided as regular text. EOT must be written in a separate line to mark end of text.

The text might be indented. Shallowest indentation common to all lines of response is ignored.

/api/:prefix/test ==>
    Hello {{rule.params.prefix}}-tester!
EOT

RFC-2822-Like Messages

This type of response doesn't implicitly affect content of resulting response. However it enables support to separately define response headers and response payload in a format similar to RFC-2822. The meaning of response headers strongly depends on actually used service. In context of HTTP service the response headers are actual headers of a RFC-2822-compliant message sent back to client.

Starting with line succeeding the one containing ==> with optionally given meta information and type selector the actual response is provided. Initial lines of response are defining response headers. Starting after first empty line of response the actual response payload is given. EOT must be written in a separate line to mark end of text.

The response payload might be indented. Shallowest indentation common to all lines of response is ignored. Due to that this response type isn't suitable for sending binary data without proper transfer encoding.

/some/actual/message AND query.type === "actual" ==> MSG
    set-cookie: mycookie=value:set
    x-folded-value: so
        me
      folded value

    Hello {{query.type}} message-tester!
EOM

Script Processing

For every incoming request the whole sequence of rules is processed. Iterating over defined rules top to bottom the first rule in sequence with matching test is picked to provide a response. Any succeeding rules are ignored for the rest of either request.

Keywords

none

Install

npm i @cepharum/mockup-service

DownloadsWeekly Downloads

3

Version

0.1.5

License

MIT

Unpacked Size

74.6 kB

Total Files

18

Last publish

Collaborators

  • avatar
  • avatar