Lesson 2 of 6
CAP service tests with cds.test
10 min
Listen to this lesson
CAP ships its own test support. cds.test starts a CAP server for your project inside the test process and gives you helpers to call it over HTTP or through the service API. Because the server runs with an in-memory database, mocked users and mocked remote services, a full service test suite runs in seconds on a laptop or in the pipeline.
Setting up
Add the test package as a development dependency with npm add -D @cap-js/cds-test; cds test -? confirms it works. Tests can run with Node's built-in test runner (node --test), with cds test (a thin wrapper around it) or with another common JavaScript test runner. CAP's documentation recommends writing runner-agnostic tests: use describe, it, the before and after hooks, and the expect function provided by cds.test, and avoid runner-specific mocking features. It also insists on one rule: call cds.test() first in each test file, before loading other CAP modules, so configuration is read from the right project. Runner-agnostic tests paid off with cds 10 (June 2026): @cap-js/cds-test 1.0 supports Vitest, and CAP is phasing out Jest, so teams that relied on Jest-specific mocking now have porting work that runner-agnostic suites avoid.
A first test file, test/fleet.test.js:const cds = require('@sap/cds')const { GET, POST, expect, defaults } = cds.test(__dirname + '/..')defaults.auth = { username: 'agent' }describe('FleetService', () => { it('creates an inquiry with notes', async () => { const { status } = await POST('/odata/v4/fleet/Inquiries', { salesOrder: '1000457', customer: '17100001', priority: 2, notes: [{ text: 'Customer called' }] }) expect(status).to.equal(201) })})
Sales order 1000457 and business partner 17100001 (Great Lakes Fleet Services) are Contoso's illustrative course data, loaded from test/data and the mocked Sales Order API, not real documents. The example uses CommonJS; in an ES module project, which is the default for projects created with cds 10, the file imports cds and passes the project folder with import.meta.dirname instead of __dirname.
What to test at service level
| Area | Example tests for the fleet app |
|---|---|
| Declarative validation | Priority 5 is rejected with 400; a missing salesOrder is rejected; an unknown topic code is rejected by @assert.target |
| Actions and handlers | closeInquiry without resolution returns 400; with resolution sets status Closed and closedBy to the caller |
| Compositions | Deep insert creates the inquiry and its notes; deleting the inquiry deletes its notes |
| Authorization | Each cell of the role matrix: 401 anonymous, 403 for an agent calling closeInquiry, 404 for another customer's inquiry |
| Remote data use | The sales order list for a customer is built from the mocked Sales Order API with the expected filter |
| Error handling | A failed remote call produces a clear message, not a crash, and does not lose the inquiry |
Service-level test areas, each with at least one positive and one negative case.
- Startcds.test launches the CAP server with in-memory SQLite, mocked auth and mocked remote services
- ArrangeReset data before each test so tests do not depend on each other; set the user
- ActSend an HTTP request with GET or POST, or call the service API through cds.connect.to
- AssertCheck status, payload and resulting database state with expect
- ReportThe runner reports results; the pipeline stores them as evidence
HTTP or service API?
cds.test supports both. HTTP calls go through the protocol adapter, so they test the OData API as consumers see it, including status codes and authorization. Calls through cds.connect.to('FleetService') skip the protocol layer, which is useful for testing service logic directly. For data isolation, the test helper offers a data reset that redeploys the database; CAP's documentation shows it in a beforeEach hook. Default request options, such as the user, can be set once with defaults and overridden per request with an auth option.
| HTTP helpers (GET, POST, ...) | Service API (cds.connect.to) | |
|---|---|---|
| Goes through | OData protocol adapter, authentication, authorization | Service layer directly |
| Best for | API behavior, status codes, role matrix | Business logic in isolation |
| Example | POST closeInquiry as agent expects 403 | Read inquiries and compare with a direct query |
| Speed | Fast | Faster |
What would you do?
Kenji's team mocks the CAP service itself with the test runner's mocking features, replacing the generic provider with stubs that return fixed data, "to make tests faster". What should the reviewer say?
- cds.test starts the real CAP service in the test process with in-memory data and mocks.
- Call cds.test() first; write runner-agnostic tests with its expect function.
- Test validation, actions, compositions, authorization, remote data use and error handling, each positive and negative.
- Use HTTP helpers for API behavior and the service API for isolated logic.
- Reset data per test; do not mock the CAP service itself.
Check your understanding · not graded
Lesson check
1. Which package provides cds.test in current CAP projects?
2. What must come first in a CAP test file?
3. Why reset data before each test?
4. Which approach tests status codes and authorization as consumers see them?