
Security News
Attackers Are Hunting High-Impact Node.js Maintainers in a Coordinated Social Engineering Campaign
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.
@loopback/testlab
Advanced tools
A collection of test utilities we use to write LoopBack tests.
Test utilities to help writing LoopBack 4 tests:
expect - behavior-driven development (BDD) style assertionssinon
supertest clients for LoopBack applicationsnpm install --save-dev @loopback/testlab
This package is typically used in tests, save it to devDependencies via
--save-dev.
import {expect} from '@loopback/testlab';
describe('Basic assertions', => {
it('asserts equal values', => {
expect({key: 'value'}).to.deepEqual({key: 'value'});
expect.exists(1);
});
});
Table of contents:
expectShould.js configured in "as-function" mode
(global Object.prototype is left intact) with an extra chaining word to.
sinonSpies, mocks and stubs. Learn more at http://sinonjs.org/.
shotStub implementation of HTTP Request and Response objects, useful for unit tests.
Besides the API provided by shot module (see
API Reference), we provide
additional APIs to better support async/await flow control and usage in
Express-based code.
There are three primary situations where you can leverage stub objects provided by Shot in your unit tests:
Use the factory function stubServerRequest to create a stub request that can
be passed to methods expecting core HTTP Request on input.
import {stubServerRequest, expect} from '@loopback/testlab';
describe('parseParams', () => {
it('parses query string arguments', () => {
const request = stubServerRequest({
method: 'GET',
url: '/api/products?count=10',
});
const args = parseParams(request, [
{name: 'count', in: 'query', type: 'number'},
]);
expect(args).to.eql([10]);
});
});
Use the factory function stubHandlerContext to create request & response stubs
and a promise to observe the actual response as received by clients.
import {stubHandlerContext, expect} from '@loopback/testlab';
describe('app REST handler', () => {
it('returns 404 with JSON body when URL not found', async () => {
const app = express();
const context = stubHandlerContext({
method: 'GET',
url: '/path-does-not-exist',
});
// Invoke Express' request handler with stubbed request/response objects
app(context.request, context.response);
// Wait until Express finishes writing the response
const actualResponse = await context.result;
// Verify the response seen by clients
expect(actualResponse.statusCode).to.equal(404);
expect(JSON.parse(actualResponse.payload)).to.containEql({
error: {
statusCode: 404,
message: 'Not Found',
},
});
});
});
Express modifies core HTTP request and response objects with additional properties and methods, it also cross-links request with response and vice versa. As a result, it's not possible to create Express Request object without the accompanying Response object.
Use the factory function stubExpressContext to create Express-flavored request
& response stubs and a promise to observe the actual response as received by
clients.
If your tested function is expecting a request object only:
import {stubExpressContext, expect} from '@loopback/testlab';
describe('operationArgsParser', () => {
it('parses body parameter', async () => {
const req = givenRequest({
url: '/',
payload: {key: 'value'},
});
const spec = givenOperationWithRequestBody({
description: 'data',
content: {'application/json': {schema: {type: 'object'}}},
});
const route = givenResolvedRoute(spec);
const args = await parseOperationArgs(req, route);
expect(args).to.eql([{key: 'value'}]);
});
function givenRequest(options?: ShotRequestOptions): Request {
return stubExpressContext(options).request;
}
});
Tests verifying code producing HTTP response can await context.result to
receive the response as returned to clients.
import {stubExpressContext, expect} from '@loopback/testlab';
describe('response writer', () => {
it('writes object result to response as JSON', async () => {
const context = stubExpressContext();
writeResultToResponse(context.response, {name: 'Joe'});
const result = await context.result;
expect(result.headers['content-type']).to.eql('application/json');
expect(result.payload).to.equal('{"name":"Joe"}');
});
});
skipIfHelper function for skipping tests when a certain condition is met. Use this
helper together with it or describe.
skipIf(someCondition, it, 'does something', async () => {
// the test code
});
Unfortunately, type inference does not work well for describe, you have to
help the compiler to figure out the correct types.
skipIf<[(this: Suite) => void], void>(
someCondition,
describe,
'some suite name',
() => {
// define the test cases
},
);
Under the hood, skipIf invokes the provided test verb by default (e.g. it).
When the provided condition was true, then it calls .skip instead (e.g.
it.skip).
skipOnTravisHelper function for skipping tests on Travis environment. If you need to skip
testing on Travis for any reason, use this helper together with it or
describe.
skipOnTravis(it, 'does something when some condition', async () => {
// the test code
});
Under the hood, skipOnTravis invokes the provided test verb by default (e.g.
it). When the helper detects Travis CI environment variables, then it calls
.skip instead (e.g. it.skip).
createRestAppClientHelper function to create a supertest client connected to a running
RestApplication. It is the responsibility of the caller to ensure that the app
is running and to stop the application after all tests are done.
Example use:
import {Client, createRestAppClient} from '@loopback/testlab';
describe('My application', () => {
app: MyApplication; // extends RestApplication
client: Client;
before(givenRunningApplication);
before(() => {
client = createRestAppClient(app);
});
after(() => app.stop());
it('invokes GET /ping', async () => {
await client.get('/ping?msg=world').expect(200);
});
});
givenHttpServerConfigHelper function for generating Travis-friendly host (127.0.0.1). This is required because Travis is not able to handle IPv6 addresses.
httpGetAsyncAsync wrapper for making HTTP GET requests.
import {httpGetAsync} from '@loopback/testlab';
const response = await httpGetAsync('http://example.com');
httpsGetAsyncAsync wrapper for making HTTPS GET requests.
import {httpsGetAsync} from '@loopback/testlab';
const response = await httpsGetAsync('https://example.com');
toJSONJSON encoding does not preserve properties that are undefined. As a result,
deepEqual checks fail because the expected model value contains these
undefined property values, while the actual result returned by REST API does
not. Use this function to convert a model instance into a data object as
returned by REST API.
import {createClientForHandler, toJSON} from '@loopback/testlab';
it('gets a todo by ID', () => {
return client
.get(`/todos/${persistedTodo.id}`)
.expect(200, toJSON(persistedTodo));
});
validateApiSpecVerify that your application API specification is a valid OpenAPI spec document.
import {validateApiSpec} from '@loopback/testlab';
import {RestServer} from '@loopback/rest';
describe('MyApp', () => {
it('has valid spec', async () => {
const app = new MyApp();
const server = await app.getServer(RestServer);
await validateApiSpec(server.getApiSpec());
});
});
createUnexpectedHttpErrorLoggerAn error logger that logs the error only when the HTTP status code is not the expected HTTP status code. This is useful when writing tests for error responses:
When we don't want any error messages printed to the console when the server responds with the expected error and the test passes.
When something else goes wrong and the server returns an unexpected error status code, and we do want an error message to be printed to the console so that we have enough information to troubleshoot the failing test.
import {createUnexpectedHttpErrorLogger} from '@loopback/testlab';
import {RestApplication} from '@loopback/rest';
describe('MyApp', () => {
it('does not log a known 401 error to console', async () => {
const app = new RestApplication();
const errorLogger = createUnexpectedHttpErrorLogger(401);
// binds the custom error logger
app.bind(SequenceActions.LOG_ERROR).to(errorLogger);
const spec = {
responses: {
/*...*/
},
};
function throwUnauthorizedError() {
throw new HttpErrors.Unauthorized('Unauthorized!');
}
app.route('get', '/', spec, throwUnauthorizedError);
await app.start();
// make `GET /` request, assert that 401 is returned
});
});
Many tests need use a temporary directory as the sandbox to mimic a tree of
files. The TestSandbox class provides such facilities to create and manage a
sandbox on the file system.
// Create a sandbox as a unique temporary subdirectory under the rootPath
const sandbox = new TestSandbox(rootPath);
const sandbox = new TestSandbox(rootPath, {subdir: true});
// Create a sandbox in the root path directly
// This is same as the old behavior
const sandbox = new TestSandbox(rootPath, {subdir: false});
// Create a sandbox in the `test1` subdirectory of the root path
const sandbox = new TestSandbox(rootPath, {subdir: 'test1'});
// To access the target directory of a sandbox
console.log(sandbox.path);
All files inside a sandbox will be removed when the sandbox is reset. We also
try to remove cache from require.
await sandbox.reset();
Removes all files and mark the sandbox unusable.
await sandbox.delete();
Recursively creates a directory within the sandbox.
await sandbox.mkdir(dir);
Copies a file from src to the TestSandbox. If copying a .js file which has an
accompanying .js.map file in the src file location, the dest file will have
its sourceMappingURL updated to point to the original file as an absolute path
so you don't need to copy the map file.
await sandbox.copyFile(src, dest);
Creates a new file and writes the given data serialized as JSON.
await sandbox.writeJsonFile(dest, data);
Creates a new file and writes the given data as a UTF-8-encoded text.
await sandbox.writeFile(dest, data);
For more info about supertest, please refer to
supertest
Run npm test from the root folder.
See all contributors.
MIT
FAQs
A collection of test utilities we use to write LoopBack tests.
The npm package @loopback/testlab receives a total of 27,927 weekly downloads. As such, @loopback/testlab popularity was classified as popular.
We found that @loopback/testlab demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 7 open source maintainers collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.

Security News
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.

Security News
Node.js has paused its bug bounty program after funding ended, removing payouts for vulnerability reports but keeping its security process unchanged.