| name | testing |
| description | Use when writing or reviewing tests for a SAP CAP application: unit tests, integration tests with cds.test, Vitest setup (recommended since CAP April 2026), jest setup, mocking services or databases, testing actions/functions, or test data management in SAP CAP Node.js projects.
|
| metadata | {"version":"1.1.0","keywords":["cds.test","Vitest","jest","integration test","unit test","mock","authentication test","@cap-js/cds-test","supertest","test data","CSV"],"related":{"service-handlers":"test service handler logic","security-auth":"test authorization with different user roles","ci-cd":"run tests in CI/CD pipelines","error-handling":"test that errors are returned correctly"}} |
Testing — CAP Best Practices
Primary reference: https://cap.cloud.sap/docs/node.js/cds-test
Vitest support (primary): https://cap.cloud.sap/docs/releases/2026/apr26
Recommended test runner: Vitest (CAP April 2026+)
cds.test now fully supports Vitest as a primary choice, while still maintaining compatibility with other test runners like Jest, Mocha, or Node's built-in test runner. Vitest is fully ESM compatible and recommended because Chai v6 (ESM) can no longer be fully provided in Jest runs — cds.test only provides a partial Chai emulation in Jest.
CDS 10+: New CAP projects are ESM-based by default ("type": "module" in package.json). Use import syntax in test files. To create a CommonJS project, remove "type": "module" from package.json.
npm install --save-dev @cap-js/cds-test vitest
vitest.config.js:
import { defineConfig } from 'vitest/config'
export default defineConfig({
test: {
testTimeout: 30000,
hookTimeout: 30000,
}
})
package.json:
{
"scripts": {
"test": "vitest run"
}
}
Basic integration test
import cds from '@sap/cds'
const { GET, POST, DELETE, expect } = cds.test('.')
describe('CatalogService', () => {
it('lists products', async () => {
const { data } = await GET('/catalog/Products')
expect(data.value).to.be.an('array')
expect(data.value.length).to.be.greaterThan(0)
})
it('rejects order with negative quantity', async () => {
const res = await POST('/catalog/submitOrder', {
product: '2b23bb4b-4ac7-4a24-ac02-aa10cabd842c',
quantity: -1
})
expect(res.status).to.equal(422)
})
})
Testing with authentication
it('admin can delete', async () => {
const { status } = await DELETE('/admin/Products(some-uuid)').as('alice')
expect(status).to.equal(204)
})
it('viewer cannot delete', async () => {
const { status } = await DELETE('/admin/Products(some-uuid)').as('carol')
expect(status).to.equal(403)
})
Mocking external services
const S4 = await cds.connect.to('S4')
vi.spyOn(S4, 'run').mockResolvedValue([{ BusinessPartner: 'BP001' }])
Testing custom actions
it('submitOrder returns success', async () => {
const { data, status } = await POST('/orders/submitOrder', {
orderID: 'some-uuid'
}).as('alice')
expect(status).to.equal(200)
expect(data.success).to.equal(true)
})
CSV test data
Put test data in db/data/. CAP loads these automatically in SQLite mode.
For test isolation, use in-memory SQLite:
{
"cds": {
"requires": {
"db": { "kind": "sqlite", "credentials": { "database": ":memory:" } }
}
}
}
Still on Jest? Known limitations
cds.test.expect is only a partial Chai emulation in Jest — not all Chai APIs work
- Use
jest.spyOn instead of vi.spyOn
- Add
--forceExit: "test": "jest --forceExit"
- Set
testTimeout: 30000 in jest.config.js
- Plan migration to Vitest — CAP recommends migrating to Vitest, or using Jest's own
expect if you need to stick with Jest
Common mistakes to avoid
-
❌ Starting new CAP projects with Jest — Chai ESM issues mean incomplete assertion support
-
✅ Use Vitest for all new projects; @cap-js/cds-test v1.0 is the stable recommended choice
-
❌ Not setting testTimeout — default 5s too short for CAP server startup
-
✅ Set testTimeout: 30000 in vitest.config.js
-
❌ Testing with a shared HANA instance
-
✅ Use SQLite :memory: for all unit and integration tests
-
❌ Forgetting .as('user') on protected endpoints — silent 401 failures
-
✅ Always specify the mock user matching the required role
-
❌ Mocking cds.db directly
-
✅ Use vi.spyOn(srv, 'run') on the connected service instance