____ _ _
| _ \ _ _ _ __ ___ _ __ (_)_ __ __ _| |_ ___ _ __ TM
| | | | | | | '_ ` _ \| '_ \| | '_ \ / _` | __/ _ \| '__|
| |_| | |_| | | | | | | |_) | | | | | (_| | || (_) | |
|____/ \__,_|_| |_| |_| .__/|_|_| |_|\__,_|\__\___/|_|
|_|
Dumpinator is an automated QA tool for REST APIs. Its mission is to compare a list of HTTP Response Headers & Bodies in different environments & versions. The current version was developed as a development tool that quickly generates API response diffs that give an idea whether major refactorings work as expected. The goal is to develop Dumpinator as an independent, scriptable CLI tool that can be used for all sorts of REST APIs. It expects a main configuration file and spec files for each API endpoint that needs to be diff'ed. Each run writes the responses as JSON files in the given directory and outputs an easy-to-read HTML report of the diff. Used in combination with Jenkins, Dumpinator can be run in a CI pipeline but also be triggered manually by the QA team each time a new API release is about to be deployed as a replacement for tedious and time consuming regression tests.
"With Dumpinator™, we are about to revolutionize the way we think about Heimdall testing and API testing in general."
A. Heinkelein, CEO Heimdall Inc.
Install Dumpinator™ globally using
$ npm install -g dumpinator
Synopsis: dp [options] [command]
Commands:
diff <left> <right> compare the given routes
diff <id> compare the given routes by a result id
show <id> show a result of the given id
run run the diff suite (default task)
help [cmd] display help for [cmd]
Options:
-h, --help output usage information
-V, --version output the version number
-v, --verbose Be more verbose
Can be used both with config files and command line arguments. Fetches the given routes and outputs a run report by comparing each left & right side response.
The run
task is the default task an will be used if no task was added to the command.
$ dp [options]
is the same as
$ dp run [options]
If no arguments are given, Dumpinator™ tries to find the default config in the current working directory. If dumpinator.conf.js
is not found, it looks for dumpinator.json
(CommonJS module) before giving up:
$ dp run
A custom config can be provided via -c
or --config
:
$ dp run -c /path/to/my/config.json # or config.js (CommonJS module)
$ dp run http://localhost/v2/my-first-route http://myapi.com/v1/my-first-route
$ dp run "POST http://localhost/v2/my-first-route" "POST http://myapi.com/v1/my-first-route"
$ dp run <left> <right> -H "content-type:application/json" -H "language:en_US" # or --header "..."
$ dp run <left> <right> -L "content-type:application/json" -L "language:en_US" # or --header-left "..."
$ dp run <left> <right> -R "content-type:application/json" -R "language:en_US" # or --header-right "..."
$ dp run -r 10 # or --rate ...
$ dp run -t "some-route-type" # or --tag "..."
This command shows a diff of two given routes or a result id. It's ok to use only the first characters as long as a unique match is found.
$ dp diff http://foo.com/my.json http://bar.com/my.json
or
$ dp diff fe345dc # or dp diff fe
Dumpinator™ accepts both JSON files and CommonJS modules which can be scripted for more flexibility.
The following config will fetch 2 routes /my-first-route
and /my-second-route
from both http://localhost/v2/
and http://myapi.com/v1/
and compare them.
module.exports = {
defaults: {
left: {
hostname: 'http://localhost/v2/'
},
right: {
hostname: 'http://myapi.com/v1/'
}
},
routes: [
{
url: '/my-first-route'
},
{
url: '/my-second-route'
}
]
};
{
"defaults": {
"left": {
"hostname": "http://localhost/v2/"
},
"right": {
"hostname": "http://myapi.com/v1/"
}
},
"routes": [
{
"url": "/my-first-route"
},
{
"url": "/my-second-route"
}
]
}
Additional headers can be added to the defaults.left
and defaults.right
sections where they get appended to each route:
module.exports = {
defaults: {
left: {
hostname: 'http://localhost/v2/',
header: {
'content-type': 'application/json',
// ...
}
},
right: {
hostname: 'http://myapi.com/v1/',
header: {
'content-type': 'application/json',
'x-some-additional-header': 'that-is-only-relevant-on-this-host',
// ...
}
}
},
routes: [
// ...
]
};
{
"defaults": {
"left": {
"hostname": "http://localhost/v2/",
"header": {
"content-type": "application/json",
...
}
},
"right": {
"hostname": "http://myapi.com/v1/",
"header": {
"content-type": "application/json",
"x-some-additional-header": "that-is-only-relevant-on-this-host",
...
}
}
},
"routes": [
...
]
}
Headers can be added to each route individually, extending & overriding default headers:
module.exports = {
defaults: {
// ...
},
routes: [
{
url: '/my-first-route',
header: {
'content-type': 'application/json',
// ...
}
},
// ...
]
};
{
"defaults": {
...
},
"routes": [
{
"url": "/my-first-route",
"header": {
"content-type": "application/json",
...
}
},
...
]
}
Additional query parameters can be added to the defaults.left
and defaults.right
sections where they get appended to each route:
module.exports = {
defaults: {
left: {
hostname: 'http://localhost/v2/',
query: {
defaultQuery: 'defaultValue',
// ...
}
},
right: {
hostname: 'http://myapi.com/v1/',
query: {
defaultQuery: 'defaultValue',
additionalQuery: 'thatIsOnlyRelevantOnThisHost',
// ...
}
}
},
routes: [
// ...
]
};
{
"defaults": {
"left": {
"hostname": "http://localhost/v2/",
"query": {
"defaultQuery": "defaultValue",
...
}
},
"right": {
"hostname": "http://myapi.com/v1/",
"query": {
"defaultQuery": "defaultValue",
"additionalQuery": "thatIsOnlyRelevantOnThisHost",
...
}
}
},
"routes": [
...
]
}
Query parameters can be added to each route individually, extending & overriding default query parameters:
module.exports = {
defaults: {
// ...
},
routes: [
{
url: '/my-first-route',
query: {
defaultQuery: 'defaultValue',
// ...
}
},
// ...
]
};
{
"defaults": {
...
},
"routes": [
{
"url": "/my-first-route",
"query": {
"defaultQuery": "defaultValue",
...
}
},
...
]
}
Setting the status
option makes a route test fail if the status code doesn't match.
{
"defaults": {
...
},
"routes": [
{
"url": "/my-first-route",
"status": 204
},
...
]
}
The method
option sets the HTTP send method which can be set in either level. GET
is the default.
Dumpinator supports these methods:
CHECKOUT
COPY
DELETE
GET
HEAD
LOCK
MERGE
MKACTIVITY
MKCOL
MOVE
M-SEARCH
NOTIFY
OPTIONS
PATCH
POST
PURGE
PUT
REPORT
SEARCH
SUBSCRIBE
TRACE
UNLOCK
UNSUBSCRIBE
{
"defaults": {
"method": "GET",
...
},
"routes": [
{
"url": "/my-first-route",
"status": 204,
"right": {
"method": "POST"
}
},
...
]
}
Callbacks can be used as hooks on any level to add some before/after logic.
before
on base level: Called before starting all tests
beforeEach
on base level: Called before each route
before
on route level: Called before a single route
after
on route level: Called after a single route
afterEach
on base level: Called after each route
after
on base level: Called after completing all tests
Callbacks are simple functions and either sync or async. If you return a promise, the callback will be handled async, otherwise sync.
module.exports = {
defaults: {
// ...
},
before: () => {
console.log('All tests started');
return Promise.resolve();
},
beforeEach: () => {
console.log('Route started');
},
after: () => {
console.log('All tests completed');
}
routes: [
{
url: '/my-first-route',
before: () => {
console.log('Individual route started');
return Promise.resolve();
},
after: () => {
console.log('Individual route completed');
return Promise.resolve();
}
},
// ...
]
};
A transform()
method can be used to transform the response before it gets stored or used for a diff. The transform method is a static method and contains the response object as first argument.
module.exports = {
defaults: {
// ...
},
routes: [
{
url: '/my-first-route',
left: {
tranform(res) {
// wrap a data level around body response
res.body = JSON.stringify({ data: JSON.parse(res.body) });
return res;
}
}
},
// ...
]
};
Sometimes, you don't care about certain header or body properties and want to ignore them. Use ignoreHeader
and ignoreBody
for that.
{
"defaults": {
"ignoreBody": [
"foo.bar",
"customer.sessionId"
],
"ignoreHeader": [
"sessionid",
"cookies"
]
},
"routes": [
{
"url": "/my-first-route",
"query": {
"defaultQuery": "defaultValue",
...
}
},
...
]
}
The API can be used directly, too.
const dumpinator = require('dumpinator');
// TODO
TODO
Copyright © 2016 maxdome GmbH
Licensed under the MIT license.