Skip to content

Commit 03f7800

Browse files
DavertMikDavertMik
andauthored
replaced joi with zod (#5250)
Co-authored-by: DavertMik <davert@testomat.io>
1 parent 0e270f2 commit 03f7800

File tree

5 files changed

+73
-62
lines changed

5 files changed

+73
-62
lines changed

docs/api.md

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -267,25 +267,33 @@ I.seeResponseContainsKeys(['name', 'email']);
267267
> ℹ️ If response is an array, it will check that every element in array have provided keys
268268
269269
However, this is a very naive approach. It won't work for arrays or nested objects.
270-
To check complex JSON structures `JSONResponse` helper uses [`joi`](https://joi.dev) library.
271-
It has rich API to validate JSON by the schema defined using JavaScript.
270+
To check complex JSON structures `JSONResponse` helper uses [`Zod`](https://zod.dev) library.
271+
It has rich API to validate JSON by the schema defined using JavaScript.
272272

273273
```js
274-
// require joi library,
274+
// import zod library,
275275
// it is installed with CodeceptJS
276-
const Joi = require('joi');
276+
import { z } from 'zod';
277277

278-
// create schema definition using Joi API
279-
const schema = Joi.object().keys({
280-
email: Joi.string().email().required(),
281-
phone: Joi.string().regex(/^\d{3}-\d{3}-\d{4}$/).required(),
282-
birthday: Joi.date().max('1-1-2004').iso()
278+
// create schema definition using Zod API
279+
const schema = z.object({
280+
email: z.string().email(),
281+
phone: z.string().regex(/^\d{3}-\d{3}-\d{4}$/),
282+
birthday: z.string().datetime().max(new Date('2004-01-01'))
283283
});
284284

285285
// check that response matches that schema
286286
I.seeResponseMatchesJsonSchema(schema);
287287
```
288288

289+
> 📋 **Migration Note**: CodeceptJS has migrated from Joi to Zod v4 for JSON schema validation.
290+
> If you have existing tests using Joi, please update them:
291+
> * Replace `const Joi = require('joi')` with `import { z } from 'zod'`
292+
> * Replace `Joi.object().keys({...})` with `z.object({...})`
293+
> * Replace `Joi.string().email()` with `z.string().email()`
294+
> * Replace `Joi.date()` with appropriate `z.string()` or `z.date()` types
295+
> * See [Zod documentation](https://zod.dev) for complete API reference
296+
289297
### Data Inclusion
290298

291299
To check that response contains expected data use `I.seeResponseContainsJson` method.

docs/helpers/JSONResponse.md

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -197,28 +197,28 @@ I.seeResponseEquals({ error: 'Not allowed' })
197197

198198
### seeResponseMatchesJsonSchema
199199

200-
Validates JSON structure of response using [joi library][4].
201-
See [joi API][5] for complete reference on usage.
200+
Validates JSON structure of response using [Zod library][4].
201+
See [Zod API][5] for complete reference on usage.
202202

203-
Use pre-initialized joi instance by passing function callback:
203+
Use pre-initialized Zod instance by passing function callback:
204204

205205
```js
206206
// response.data is { name: 'jon', id: 1 }
207207

208-
I.seeResponseMatchesJsonSchema(joi => {
209-
return joi.object({
210-
name: joi.string(),
211-
id: joi.number()
208+
I.seeResponseMatchesJsonSchema(z => {
209+
return z.object({
210+
name: z.string(),
211+
id: z.number()
212212
})
213213
});
214214

215215
// or pass a valid schema
216-
const joi = require('joi');
216+
import { z } from 'zod';
217217

218-
I.seeResponseMatchesJsonSchema(joi.object({
219-
name: joi.string(),
220-
id: joi.number();
221-
});
218+
I.seeResponseMatchesJsonSchema(z.object({
219+
name: z.string(),
220+
id: z.number()
221+
}));
222222
```
223223

224224
#### Parameters
@@ -248,8 +248,8 @@ I.seeResponseValidByCallback(({ data, status }) => {
248248

249249
[3]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array
250250

251-
[4]: https://joi.dev
251+
[4]: https://zod.dev
252252

253-
[5]: https://joi.dev/api/
253+
[5]: https://zod.dev/
254254

255255
[6]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function

lib/helper/JSONResponse.js

Lines changed: 22 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import Helper from '@codeceptjs/helper'
22
import assert from 'assert'
3-
import joi from 'joi'
3+
import { z } from 'zod'
44

55
/**
66
* This helper allows performing assertions on JSON responses paired with following helpers:
@@ -87,16 +87,7 @@ class JSONResponse extends Helper {
8787
this.response = null
8888
}
8989

90-
static _checkRequirements() {
91-
try {
92-
// In ESM, joi is already imported at the top
93-
// The import will fail at module load time if joi is missing
94-
return null
95-
} catch (e) {
96-
return ['joi']
97-
}
98-
}
99-
90+
10091
/**
10192
* Checks that response code is equal to the provided one
10293
*
@@ -308,28 +299,28 @@ class JSONResponse extends Helper {
308299
}
309300

310301
/**
311-
* Validates JSON structure of response using [joi library](https://joi.dev).
312-
* See [joi API](https://joi.dev/api/) for complete reference on usage.
302+
* Validates JSON structure of response using [Zod library](https://zod.dev).
303+
* See [Zod API](https://zod.dev/) for complete reference on usage.
313304
*
314-
* Use pre-initialized joi instance by passing function callback:
305+
* Use pre-initialized Zod instance by passing function callback:
315306
*
316307
* ```js
317308
* // response.data is { name: 'jon', id: 1 }
318309
*
319-
* I.seeResponseMatchesJsonSchema(joi => {
320-
* return joi.object({
321-
* name: joi.string(),
322-
* id: joi.number()
310+
* I.seeResponseMatchesJsonSchema(z => {
311+
* return z.object({
312+
* name: z.string(),
313+
* id: z.number()
323314
* })
324315
* });
325316
*
326317
* // or pass a valid schema
327-
* const joi = require('joi');
318+
* import { z } from 'zod';
328319
*
329-
* I.seeResponseMatchesJsonSchema(joi.object({
330-
* name: joi.string(),
331-
* id: joi.number();
332-
* });
320+
* I.seeResponseMatchesJsonSchema(z.object({
321+
* name: z.string(),
322+
* id: z.number()
323+
* }));
333324
* ```
334325
*
335326
* @param {any} fnOrSchema
@@ -338,14 +329,17 @@ class JSONResponse extends Helper {
338329
this._checkResponseReady()
339330
let schema = fnOrSchema
340331
if (typeof fnOrSchema === 'function') {
341-
schema = fnOrSchema(joi)
332+
schema = fnOrSchema(z)
342333
const body = fnOrSchema.toString()
343334
fnOrSchema.toString = () => `${body.split('\n')[1]}...`
344335
}
345-
if (!schema) throw new Error('Empty Joi schema provided, see https://joi.dev/ for details')
346-
if (!joi.isSchema(schema)) throw new Error('Invalid Joi schema provided, see https://joi.dev/ for details')
347-
schema.toString = () => schema.describe()
348-
joi.assert(this.response.data, schema)
336+
if (!schema) throw new Error('Empty Zod schema provided, see https://zod.dev/ for details')
337+
if (!(schema instanceof z.ZodType)) throw new Error('Invalid Zod schema provided, see https://zod.dev/ for details')
338+
schema.toString = () => schema._def.description || JSON.stringify(schema._def)
339+
const result = schema.parse(this.response.data)
340+
if (!result) {
341+
throw new Error('Schema validation failed')
342+
}
349343
}
350344

351345
_checkResponseReady() {

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@
106106
"html-minifier-terser": "7.2.0",
107107
"inquirer": "12.9.2",
108108
"invisi-data": "^1.1.2",
109-
"joi": "17.13.3",
109+
"zod": "^4.0.0",
110110
"js-beautify": "1.15.4",
111111
"lodash.clonedeep": "4.5.0",
112112
"lodash.merge": "4.6.2",

test/helper/JSONResponse_test.js

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import chai from 'chai'
2-
import joi from 'joi'
2+
import { z } from 'zod'
33
import { JSONResponse } from '../../lib/helper/JSONResponse.js'
44
import Container from '../../lib/container.js'
55
import * as codeceptjs from '../../lib/index.js'
@@ -149,16 +149,16 @@ describe('JSONResponse', () => {
149149
expect(fn.toString()).to.include('expect(data).to.have')
150150
})
151151

152-
it('should check for json by joi schema', () => {
152+
it('should check for json by zod schema', () => {
153153
restHelper.config.onResponse({ data })
154-
const schema = joi.object({
155-
posts: joi.array().items({
156-
id: joi.number(),
157-
author: joi.string(),
158-
title: joi.string(),
159-
}),
160-
user: joi.object({
161-
name: joi.string(),
154+
const schema = z.object({
155+
posts: z.array(z.object({
156+
id: z.number(),
157+
author: z.string(),
158+
title: z.string(),
159+
})),
160+
user: z.object({
161+
name: z.string(),
162162
}),
163163
})
164164
const fn = () => {
@@ -167,5 +167,14 @@ describe('JSONResponse', () => {
167167
I.seeResponseMatchesJsonSchema(fn)
168168
I.seeResponseMatchesJsonSchema(schema)
169169
})
170+
171+
it('should throw error when zod validation fails', () => {
172+
restHelper.config.onResponse({ data: { name: 'invalid', age: 'not_a_number' } })
173+
const schema = z.object({
174+
name: z.string(),
175+
age: z.number(),
176+
})
177+
expect(() => I.seeResponseMatchesJsonSchema(schema)).to.throw('Schema validation failed')
178+
})
170179
})
171180
})

0 commit comments

Comments
 (0)