Skip to main content

ApiExpect & Matchers

ApiExpect is the fluent assertion object attached to every response as res.expect. It provides a two-level assertion chain: the first level checks the HTTP outcome, the second level (returned by the first) narrows assertions to the response shape.

Entry point: res.expect​

OpraTestClient.getResponse() returns an HttpResponse extended with an .expect property of type ApiExpect.

const res = await testClient.get('/customers').getResponse();
res.expect /* ApiExpect */

ApiExpect​

toSuccess(status?)​

Asserts the response is successful (2xx–3xx). Pass an exact status code to assert a specific value.

res.expect.toSuccess(); // any 200-399
res.expect.toSuccess(HttpStatusCode.OK); // exactly 200
res.expect.toSuccess(HttpStatusCode.CREATED); // exactly 201

Returns this — you can chain payload assertions directly.

toFail(status?)​

Asserts the response is a failure (4xx–5xx). Pass an exact status code to assert a specific value.

res.expect.toFail(); // any 400-599
res.expect.toFail(HttpStatusCode.UNAUTHORIZED); // exactly 401
res.expect.toFail(HttpStatusCode.FORBIDDEN); // exactly 403
res.expect.toFail(HttpStatusCode.NOT_FOUND); // exactly 404

Returns an ApiExpectError for further error body assertions.

toReturnCollection()​

Asserts the response carries an OPRA collection payload (Content-Type: application/opra.response+json with an array payload).

res.expect.toSuccess(HttpStatusCode.OK).toReturnCollection();

Returns an ApiExpectCollection for further payload assertions.

toReturnObject(contentType?)​

Asserts the response carries a single-object OPRA payload.

res.expect.toSuccess(HttpStatusCode.OK).toReturnObject();

Returns an ApiExpectObject for further payload assertions.

toReturnOperationResult()​

Asserts the response carries an OPRA operation result (no payload field — used for delete/update-many operations).

res.expect.toSuccess(HttpStatusCode.OK).toReturnOperationResult();

Returns an ApiExpectOperationResult for further assertions.


ApiExpectCollection​

Returned by toReturnCollection(). All methods return this so they chain.

toReturnItems(min?, max?)​

Asserts the payload array has at least min items (default 1) and at most max items.

res.expect.toSuccess().toReturnCollection().toReturnItems(); // at least 1
res.expect.toSuccess().toReturnCollection().toReturnItems(5); // at least 5
res.expect.toSuccess().toReturnCollection().toReturnItems(1, 10); // between 1 and 10

toContainTotalMatches(min?, max?)​

Asserts the totalMatches field in the response envelope.

res.expect.toSuccess().toReturnCollection().toContainTotalMatches(100);

toMatch(expected)​

Asserts every item in the payload matches the expected object (subset match — extra fields are allowed).

const data = { _id: uid(25), name: 'Acme' };
res.expect.toSuccess().toReturnCollection().toMatch(data);

toContainFields(fields)​

Asserts every item in the payload contains the specified fields.

res.expect.toSuccess().toReturnCollection().toContainFields(['_id', 'name']);

toContainAllFields(fields)​

Asserts every item in the payload contains exactly the specified fields and no others.

res.expect.toSuccess().toReturnCollection().toContainAllFields(['_id', 'name', 'active']);

toBeSortedBy(fields)​

Asserts the payload array is sorted by the given fields (ascending).

res.expect.toSuccess().toReturnCollection().toBeSortedBy('familyName');
res.expect.toSuccess().toReturnCollection().toBeSortedBy(['familyName', 'givenName']);

toBeFilteredBy(filter)​

Asserts every item in the payload satisfies the given OPRA filter expression.

res.expect
.toSuccess()
.toReturnCollection()
.toBeFilteredBy('address.countryCode = "US"');

.not​

Negates the next assertion:

res.expect.toSuccess().toReturnCollection().not.toReturnItems(10, 10);

ApiExpectObject​

Returned by toReturnObject(). All methods return this.

toMatch(expected)​

Asserts the payload object contains the expected fields (subset match).

res.expect.toSuccess().toReturnObject().toMatch({ _id: '42', name: 'Acme' });

toContainFields(fields)​

Asserts the payload object contains the specified fields.

res.expect.toSuccess().toReturnObject().toContainFields(['_id', 'name']);

toContainAllFields(fields)​

Asserts the payload object contains exactly the specified fields.

res.expect.toSuccess().toReturnObject().toContainAllFields(['_id', 'name', 'active']);

.not​

res.expect.toSuccess().toReturnObject().not.toContainFields(['password']);

ApiExpectError​

Returned by toFail(). Provides a single method:

toMatch(expected)​

Asserts the error body contains a matching error issue. Accepts a string (matched against message), a RegExp, or a partial MatchingErrorIssue object.

// String: matched against `message`
res.expect.toFail(HttpStatusCode.UNAUTHORIZED).toMatch('Unauthorized');

// RegExp: matched against `message`
res.expect.toFail(HttpStatusCode.FORBIDDEN).toMatch(/permission denied/i);

// Object: matched against all provided fields
res.expect.toFail(HttpStatusCode.BAD_REQUEST).toMatch({
message: 'Validation failed',
code: 'VALIDATION_ERROR',
});

MatchingErrorIssue fields: message, system, code, details, diagnostics — each accepts a literal value or RegExp.


ApiExpectOperationResult​

Returned by toReturnOperationResult().

toBeAffected(min?, max?)​

Asserts the affected count in the operation result.

res.expect.toSuccess().toReturnOperationResult().toBeAffected(); // at least 1
res.expect.toSuccess().toReturnOperationResult().toBeAffected(3); // at least 3
res.expect.toSuccess().toReturnOperationResult().toBeAffected(1, 5); // between 1 and 5

.not​

res.expect.toSuccess().toReturnOperationResult().not.toBeAffected(0, 0);

Full example​

import { UserRole } from './models';
import { CustomerService } from './services';
import { HttpStatusCode, uid } from '@opra/common';
import { OpraTestClient } from '@opra/testing';
import * as sinon from 'sinon';
import { TestApp } from '#test-app';

describe('e2e: /Customers', () => {
let testApp: TestApp;
let testClient: OpraTestClient;
let customerService: CustomerService;

before(async () => {
testApp = await TestApp.create();
testClient = testApp.client;
customerService = testApp.moduleRef.get(CustomerService);
});
after(() => testApp?.close());
beforeEach(() => testApp?.restoreUser());
afterEach(() => testApp?.restoreUser());

describe('"findMany" operation', () => {
it('Should return a collection', async () => {
testApp.user.roles = [UserRole.SUPPORT_USER];
const data = { _id: uid(25), givenName: 'Jane' };
const spy = sinon.stub(customerService, 'findMany').returns([data] as any);

const res = await testClient
.get('/customers')
.param({ limit: 10 })
.getResponse();

res.expect
.toSuccess(HttpStatusCode.OK)
.toReturnCollection()
.toReturnItems(1)
.toMatch(data);

spy.restore();
});

it('Should reject anonymous requests', async () => {
testApp.user._id = '';
const res = await testClient.get('/customers').getResponse();
res.expect.toFail(HttpStatusCode.UNAUTHORIZED);
});

it('Should forbid unauthorized roles', async () => {
testApp.user.roles = [UserRole.GUEST];
const res = await testClient.get('/customers').getResponse();
res.expect.toFail(HttpStatusCode.FORBIDDEN);
});
});

describe('"get" operation', () => {
it('Should return a single object', async () => {
const data = { _id: uid(25) };
const spy = sinon.stub(customerService, 'get').returns(data as any);

const res = await testClient.get(`/customer@${data._id}`).getResponse();

res.expect
.toSuccess(HttpStatusCode.OK)
.toReturnObject()
.toMatch(data);

spy.restore();
});
});

describe('"deleteMany" operation', () => {
it('Should report affected rows', async () => {
testApp.user.roles = [UserRole.SYSTEM_ADMIN];
const spy = sinon.stub(customerService, 'deleteMany').returns({ affected: 3 } as any);

const res = await testClient
.delete('/customers')
.param({ filter: 'active = false' })
.getResponse();

res.expect
.toSuccess(HttpStatusCode.OK)
.toReturnOperationResult()
.toBeAffected(1);

spy.restore();
});
});
});