Implicit Assertions Are More Readable

Cypress can test the current subject using implicit assertions that I find more readable than the explicit Chai assertions.

Recently I saw a Linkedin post that shows "before & after" API test using Cypress. Here is the screenshot included in the post.

Before and after API test using cy.request command

Right away, I want to say that the "after" test is better. It checks the response status first, then confirms the types and ranges of values, rather than hard codes the new item's id and creation date. The "after" test will probably pass, while the first test will probably fail really quickly on new runs. I like the general idea. But I think there is room for improvement.

Give explicit assertions descriptions

First, let's give each assertion a description to know what fails when it does.

1
2
3
4
5
6
// instead of errors like "expected 201, got 404"
expect(res.status).to.eq(201)
// prefer descriptive assertions
// that produce errors like
// "status: expected 201, got 404"
expect(res.status, 'response status').to.eq(201)

Use precise range rather than "closeTo" assertion

The "after" test uses a "closeTo" assertion to check the newly created item's timestamp

1
2
// the created date should be close to the current timestamp +/- 1 minute
expect(Date.parse(res.body.createdAt)).to.be.closeTo(Date.now(), 60000)

Can the item have a "createdAt" date be 30 seconds in the future? Unlikely, but our test allows it. I would rewrite the test to be

1
2
3
const now = +Date.now()
const oneMinuteEarlier = now - 60_000
expect(Date.parse(res.body.createdAt)).to.be.within(oneMinuteEarlier, now)

Use implicit assertions

All our assertions check the response body and the response status. Let's remove the verbosity and use implicit assertions to verify the properties. It is easy to do using known values

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// explicit assertions (224 characters)
cy.request('POST', '/api/v1/orders', payload)
.then((res) => {
expect(res.body.id).to.eq(1042)
expect(res.body.protocol) .to.eq('PRT-2026-0042')
expect(res.body.createdAt).to.eq('2026-08-27T14:32:10.482Z')
})
// implicit assertions (179 characters)
cy.request('POST', '/api/v1/orders', payload)
.its('body')
.should('deep.include', {
id: 1042,
protocol: 'PRT-2026-0042',
createdAt: '2026-08-27T14:32:10.482Z'
})

We just shortened the test by 20%, but we also removed lots of duplicate noise: we know the assertions check the same res.body object. But what about the "after" test? We don't know the precise values, how can we use implicit assertions in this case?

Use cy-spok with implicit assertions

To take it to the next level, we can "wrap" the assertions with cy-spok provided utility function.

1
2
3
4
5
6
7
8
9
10
11
import spok from 'cy-spok'

cy.request('POST', '/api/v1/orders', payload)
.its('body')
.should(
spok({
id: 1042,
protocol: 'PRT-2026-0042',
createdAt: '2026-08-27T14:32:10.482Z'
})
)

Ok, now let's move to the "after" example; spok can verify nested objects just as easily. We can even use named predicates for properties

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import spok from 'cy-spok'

it('creates an object', () => {
const payload = {}

const lastMinute = (str) => {
const createdAt = Date.parse(str)
const now = +Date.now()
const oneMinuteEarlier = now - 60_000
return createdAt >= oneMinuteEarlier && createdAt <= now
}

cy.request('POST', '/api/v1/orders', payload).should(
spok({
status: 201,
body: {
id: spok.number,
protocol: spok.test(/^PRT-\d{4}-\d{4}$/),
createdAt: lastMinute,
},
}),
)
})

The entire response object, not matter how deeply nested can be checked in a single shot using cy-spok and its collection of helpers. I think such form is more powerful, more expressive, and is easier to read on the screen.

Testing response object using cy-spok

Tip: I have written lots of blog posts showing how to use cy-spok, see the links in the README file.