lb-declarative-e2e-test
allows to write tests for Loopback.io in a object definition style.
{
name: 'admin CAN create',
verb: 'post',
auth: usersCredentials.admin,
body: {some: 'value'},
url: '/some/url/',
expect: 200
}
It combines and exposes API from Mocha and supertest. The test generation logic has been moved to declarative-test-structure-generator
, check it out for the full API doc.
The steps
option was added in the latest release and allows to perform multiple requests in a single test, read more…
A demo example is available on Github.
The main motivation was to reduce the boilerplate code for every e2e tests.
In the case of a simple GET request, the test is very concise. However, as soon as the request requires authentication, a first post request is required.
In the past, I abstracted this logic in separate functions and the complexity increased. Today I hope lb-declarative-e2e-test
will help reduce the boilerplate code of many developers.
Please share your feedback and report the encountered issues on the project’s issues page.
npm install --save-dev lb-declarative-e2e-test
# or
npm i -D lb-declarative-e2e-test
const lbe2e = require('lb-declarative-e2e-test');
const server = require('../server');
lbe2e(server, {
'Read access': {
tests: [
{
name: 'unauthenticated CANNOT read',
verb: 'get',
url: '/some/url/',
expect: 401
},
{
name: 'admin CAN read data',
verb: 'get',
auth: {email: 'admin@test.server.com', password: 'test.admin'},
url: '/some/url/',
expect: 200
}
]
}
});
This code defines a test suite Read access
and two test cases
unauthenticated CANNOT read
Sends an anonymous GET
request and tests the response status is 401
admin CAN read data
Sends a first requests to the default login endpoint to authenticate the user. Then sends an authenticated GET
request and tests the response status is 200
From here, read the test suite definition and the test definition.
It extends the definition from declarative-test-structure-generator
=> test-suite-definition, accepts the following:
{
skip: {boolean}
only: {boolean}
before: {function | Array[function]}
beforeEach: {function | Array[function]}
after: {function | Array[function]}
afterEach: {function | Array[function]}
tests: {Array[TestDefinition] | Object<string, TestSuiteDefinition>}
}
See the full test suite definition API for more details.
It extends the definition from declarative-test-structure-generator
=> test-definition, accepts the following:
{
name: {string}
skip: {boolean}
only: {boolean}
url: {string | function}
verb: {string}
headers: {Object}
auth: {string | Object | Array[string | Object] | function}
body: {Object | function | *}
expect: {Object | *}
error: {function}
}
Example: On the example below, userModels
can be set during a before
or beforeEach
hook.
{
name: 'user CAN read his OWN details',
verb: 'get',
url: () => `/api/users/${userModels[0].id}`,
auth: () => ({email: userModels[0].email, password: userModels[0].password}),
expect: 200
},
name
, skip
and only
: see the full test definition API for more details.
The tested url
can be passed as a string
or as a callback function
returning a string
.
The callback value is only evaluated when configuring the request (after before
/ beforeEach
hooks).
The verb / HTTP method to use for the request.
All verbs supported by supertest are supported, e.g. get
, post
, put
, patch
, delete
, …
The headers
is an Object
mapping the key-value pairs. The pairs are merged over the headers in the global config
The auth
should be used for authenticated requests. Custom login endpoint can be configured in the global config.
The following options are supported:
string
: provides the tokenId to use for the request. It is used directly on the Authorization
header and the request is sent without prior login.Object
: provides the credentials to use for the request (the Object provided is sent as is).Array[string|Object]
: An array of any of the above.function => string|Object|Array[string|Object]
: A callback returning any of the above (lazy evaluated value).Object
: an object serialized to JSON before being sent.function
: a callback returning body.The value or callback value of body
is passed directly to supertest request.send
.
{*}
is passed directly to supertest.expect()
, it can be used to test: // check the HTTP status
{
// ...
expect: 200
}
// check the exact value of the body
{
// ...
expect: {foo: 'bar'}
}
// callback with response
{
// ...
expect: response=>{ /* test response */ }
}
expect.headers
and expect.body
are passed to supertest.expect()
{
// ...
expect: {
headers:{
status: 200,
'Content-Type': /json/
},
body: {
foo: 'bar'
}
}
}
Note: status
and Status-Code
are passed without the key to supertest.expect(value)
expect.body
. Only evaluates the value when performing the test {
// ...
expect: {
body: () => ({foo: 'bar'})
}
}
Sometimes it is not easy to test something with only one request. The option steps
allows to perform multiple requests in a single test.
All the options from the test definition are inherited in the step definition. On the example below, auth
is inherited by the steps.
{
name: 'access token should be voided after logout',
auth: () => tokenId,
steps: [
{
url: '/api/users/logout',
verb: 'post',
expect: 204
},
{
verb: 'get',
url: () => `/api/users/${userModels[0].id}`,
expect: 401
}
]
}
The step definition can also be lazy evaluated. It is particularly useful when a step needs an information from the previous step.
{
name: 'some test with lazy evaluated step definition',
steps: [
{
url: '/api/users/',
verb: 'post',
body: factory(),
expect: 200
},
step0Response => {
return {
url: `/api/users/${step0Response.body.id}?filter[include]=scores`,
verb: 'get',
expect: resp => {
expect(resp.body).to.deep.match(expectedBody);
}
};
}
]
}
Note: deep match part of chai-deep-match
plugin.
The error
is an optional callback. When provided, it will be called with the test error and the request’s response object.
See Debug a failed test for more details.
All the config below are optional, see how to specify a global config object.
{
baseUrl: 'base/url/v1',
headers: {
'Accept': 'application/json',
'Accept-Language': 'en-US'
},
auth: {
url: '/CustomUserModel/login/'
},
expect: {
headers: {
'content-encoding': 'gzip',
'x-frame-options': 'DENY'
}
},
error: err => {
console.error(err);
}
}
The baseUrl
is prepended to the test url
.
The headers
is merged with the headers
defined in the test definition. The test definition headers
takes precedence over the global config headers
.
The auth.url
configures the login endpoint for the authenticated requests, defaults to /api/users/login
.
IMPORTANT: auth.url
should be specified when the LB app extends the built-in User
model.
The expect.headers
is merged with the expect.headers
defined in the test definition. The test definition expect.headers
takes precedence over the global config expect.headers
.
The error
is an optional callback, here it is configured for all tests. When provided, it will be called with the test error and the request’s response object.
The test definition structure is not limited to a single level. As in Mocha, there is no limit to the amount of nesting.
See declarative-test-structure-generator
=> Test suites definition structure
lbe2e
accepts 2 or 3 arguments:
lbe2e(server, testsSuite);
// or
lbe2e(server, testConfig, testsSuite);
It is possible to run one or many function at different phase of the test. See declarative-test-structure-generator
=> Test hooks
TIP: Use the hook feature when you need to set some test data before the tests. See Mocha: Asynchronous hooks
See declarative-test-structure-generator
=> Run only / skip
It is possible to test the same request with a batch of users.
{
// ...
auth: [
'user-a-token-id',
{username: 'user-b', password: 'user-b-pass'},
{email: 'user-c@app.com', password: 'user-c-pass'}
]
}
See the auth
in the test definition.
TIP: It is a convenient way to test negative cases for ACL.
It is possible to register an error
callback for when the test fails. It could be either in the general config or on the test definition.
{
// ...
error: err => {
console.log(err);
}
}
This callback will be called with the following object:
{
error: {Error},
response: {Response}
}
lb-declarative-e2e-test
uses debug to log information during the tests.
You can view these logs by setting the DEBUG
env variable to lb-declarative-e2e-test
.
DEBUG=lb-declarative-e2e-test npm test