api-auditor
A NodeJS library for integration testing.
NPM: https://www.npmjs.com/package/@amakovskyi/api-auditor
Source code: https://github.com/amakovskyi/api-auditor
Matchers. Validate API responses easily
Matchers API allows to easily validate objects.
EXAMPLE
Assume we need to validate API response.
let response = client.get('https://api.com/myUserInfo')
Response content:
{
"id": "98837f2e-69f9-4083-b3f6-a9f90699a0bd",
"firstName": "John",
"lastName": "Doe",
"numberOfPosts": 18,
"description": null
}
It needed to validate only id format and first/last name.
Pure Jest way:
expect(UuidUtils.isValid(response.id)).toBeThruly()
expect(response.firstName).toEqual('John')
expect(response.lastName).toEqual('Doe')
api-auditor matchers way:
validateMatch(response, {
id: Matchers.uuid(),
firstName: 'John',
lastName: 'Doe',
})
It needed to validate general response format.
Pure Jest way:
expect(UuidUtils.isValid(response.id)).toBeThruly()
expect(typeof response.firstName).toEqual('string')
expect(typeof response.lastName).toEqual('string')
expect(typeof response.numberOfPosts).toEqual('number')
expect(Number.isInteger(response.numberOfPosts)).toBeThruly()
if (response.description != null) {
expect(typeof response.description).toEqual('string')
}
api-auditor matchers way:
validateMatch(response, {
id: Matchers.uuid(),
firstName: Matchers.string(),
lastName: Matchers.string(),
numberOfPosts: Matchers.number({ shouldBeInteger: true }),
description: Matchers.string({ canBeNull: true }),
})
In addition to simple and clear way of writing the code there will be a clear and understandable diff in console log in case of match error.
HOW TO USE
validateMatch(objectToValidate, expectedMatch)
expectedMatch
need contain only fields needed to validate. Anything else, which is not described
in expectedMatch
but present in objectToValidate
is ignored, so it is possible to put only those fields
which needed to be validated in objectToValidate
.
VALUES
Place any primitive value to validate strict equality: undefined
, null
, string
,number
etc.
validateMatch(objectToValidate, {
index: 1,
name: 'Some name',
description: null,
})
OBJECTS
Place object inside object or array to validate object contents. Fields, not included in match, are not validated.
validateMatch(objectToValidate, {
someInnerObject: {
index: 1,
id: Matchers.uuid(),
name: 'Some name',
innerItems: ArrayMatchers.any(),
}
})
ARRAYS
Place items inside array to validate array contents directly.
validateMatch(arrayToValidate, [1, 2, 3])
validateMatch(arrayToValidate, [Matchers.number(), Matchers.string()])
validateMatch(arrayToValidate, [
{ index: 1, id: Matchers.uuid(), name: 'One' },
{ index: 2, id: Matchers.uuid(), name: 'Two' },
{ index: 3, id: Matchers.uuid(), name: 'Three' },
])
COMPOSITE MATCHERS
matchAll(matches: any[])
Matcher which validates object for all matches
and raises first unmatched error in case if validation fail.
matchAny(matches: any[])
Matcher which validates object for first successful match from matches
and raises error in case there are no
successful matches.
HOW IT WORKS
// make a structural copy of "data" variable with expectation it matches to "innerExpectedMatcher"
let expectedMatch = ValueMatcher.copyWithExpectedMatch(data, expectedMatcher)
// natural Jest comparison way
expect(data).toEqual(expectedMatch);
ValueMatcher.copyWithExpectedMatch
just making comparable copy of data
, which is named comparison value.
After that natural Jest comparator is used. In case, if data
is not matches to expectedMatcher
then ValueMatcher.copyWithExpectedMatch
will generate error object, which will raise comparison error
inside expect(data).toEqual(expectedMatch)
. With this way log exactly shows place and structure of matcher
failure, easy to read and understand.
FULL LIST OF MATCHERS
Below is list of all matcher with link to documentation
CUSTOM VALUE MATCHERS
It is possible to create you own matchers.
Type 1. Standard matcher with auto check for canBeNull
and optional
before pass value in you match
validation code.
export function ownMatcher(options?: {
canBeNull?: boolean,
optional?: boolean,
yourCustomOption?: boolean,
}) {
return valueMatcher('ownMatcher', options, value => {
if (value instanceof YourType) {
return ValueMatcher.typeError('YourType');
}
if (yourCustomOption) {
// DO CHECK
}
return ValueMatcher.success();
});
}
Type 2: Matcher with only your own match validation checks.
export function ownCustomMatcher(options?: {
yourCustomOption?: boolean,
}) {
return customValueMatcher('ownCustomMatcher', options, value => {
if (value == null) {
return ValueMatcher.error('Message');
}
if (value instanceof SomeType) {
return ValueMatcher.typeError('SomeType');
}
if (yourCustomOption) {
// DO CHECK
}
return ValueMatcher.success();
});
}
Type 1 matcher automatically do checks for options canBeNull
and optional
if they are
passed and by default does not accept missing value, undefined
or null
no matter is canBeNull
and optional
passed at all. Type 2 immediately passes value to you validation code.
You need to return one of following from your validation code:
-
ValueMatcher.success()
- validation is successful -
ValueMatcher.error(message)
- validation is failed, pass a corresponding message to it -
ValueMatcher.typeError(typeName)
- validation is failed because of wrong type ofvalue
-
ValueMatcher.value(value)
- specific case when you need to return comparison value. When previous error will build a specific error object which will raise specific error in comparison log, this value will be just compared to actual value and raise natural diff in case if they are different.
Using inner matcher validations.
If you try to use validateMatch()
inside validation code it will just throw error. If it needed to build inner
validation use specific methods:
let expectedMatch = ValueMatcher.copyWithExpectedMatch(value, innerExpectedMatcher)
let isMatch = MatcherUtils.isFullyEquals(expectedMatch, value)
In result isMatch
will contain your match result, when expectedMatch will contain comparison value.
Random
Useful random utils for generating random values, and also randomizing arrays.
Pool
Under development