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();
});
});
});