or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

assertions.mdhttp-client.mdhttp-utilities.mdindex.mdrequest-response-mocking.mdtest-doubles.mdtest-sandbox.mdvalidation-helpers.md

validation-helpers.mddocs/

0

# Validation and Helpers

1

2

OpenAPI/Swagger specification validation, JSON conversion utilities, test skipping helpers, and HTTP error logging.

3

4

## Capabilities

5

6

### API Specification Validation

7

8

OpenAPI/Swagger specification validation using the oas-validator library.

9

10

```typescript { .api }

11

/**

12

* Validates OpenAPI/Swagger specifications for correctness

13

* @param spec - OpenAPI specification object to validate

14

* @throws Error if specification is invalid

15

*/

16

function validateApiSpec(spec: any): Promise<void>;

17

```

18

19

**Usage Examples:**

20

21

```typescript

22

import { validateApiSpec, expect } from "@loopback/testlab";

23

24

// Valid OpenAPI 3.0 specification

25

const validSpec = {

26

openapi: "3.0.0",

27

info: {

28

title: "Test API",

29

version: "1.0.0"

30

},

31

paths: {

32

"/users": {

33

get: {

34

responses: {

35

"200": {

36

description: "List of users",

37

content: {

38

"application/json": {

39

schema: {

40

type: "array",

41

items: {

42

type: "object",

43

properties: {

44

id: { type: "string" },

45

name: { type: "string" }

46

}

47

}

48

}

49

}

50

}

51

}

52

}

53

}

54

}

55

}

56

};

57

58

// Validate specification - should not throw

59

await validateApiSpec(validSpec);

60

61

// Test invalid specification

62

const invalidSpec = {

63

openapi: "3.0.0",

64

// Missing required 'info' property

65

paths: {}

66

};

67

68

try {

69

await validateApiSpec(invalidSpec);

70

throw new Error("Should have thrown validation error");

71

} catch (error) {

72

expect(error.message).to.match(/invalid/i);

73

}

74

75

// Use in API testing

76

describe("API Specification Tests", () => {

77

it("should have valid OpenAPI spec", async () => {

78

const spec = loadApiSpecification();

79

await validateApiSpec(spec);

80

});

81

82

it("should validate generated spec", async () => {

83

const app = createTestApplication();

84

const spec = app.getApiSpecification();

85

await validateApiSpec(spec);

86

});

87

});

88

```

89

90

### JSON Conversion

91

92

Utility function for converting values to JSON-serializable format, handling edge cases.

93

94

```typescript { .api }

95

/**

96

* JSON encoding utility that handles edge cases

97

* Converts values to JSON-serializable format by removing undefined properties

98

* and handling special types like Date and Function

99

*/

100

function toJSON<T>(value: T): T;

101

102

// Specific overloads for different types

103

function toJSON(value: Date): string;

104

function toJSON(value: Function): undefined;

105

function toJSON(value: unknown[]): unknown[];

106

function toJSON(value: object): object;

107

function toJSON(value: undefined): undefined;

108

function toJSON(value: null): null;

109

function toJSON(value: number): number;

110

function toJSON(value: boolean): boolean;

111

function toJSON(value: string): string;

112

113

// Union type overloads

114

function toJSON(value: unknown[] | null): unknown[] | null;

115

function toJSON(value: unknown[] | undefined): unknown[] | undefined;

116

function toJSON(value: unknown[] | null | undefined): unknown[] | null | undefined;

117

function toJSON(value: object | null): object | null;

118

function toJSON(value: object | undefined): object | undefined;

119

function toJSON(value: object | null | undefined): object | null | undefined;

120

```

121

122

**Usage Examples:**

123

124

```typescript

125

import { toJSON, expect } from "@loopback/testlab";

126

127

// Handle Date objects

128

const date = new Date("2023-01-01T00:00:00.000Z");

129

const dateJson = toJSON(date);

130

expect(dateJson).to.equal("2023-01-01T00:00:00.000Z");

131

132

// Handle Functions (converted to undefined)

133

const func = () => "hello";

134

const funcJson = toJSON(func);

135

expect(funcJson).to.be.undefined();

136

137

// Handle objects with undefined properties

138

const objWithUndefined = {

139

name: "Alice",

140

age: undefined,

141

active: true,

142

metadata: {

143

created: new Date("2023-01-01"),

144

updated: undefined

145

}

146

};

147

148

const cleanObj = toJSON(objWithUndefined);

149

// undefined properties are removed during JSON conversion

150

expect(cleanObj).to.not.have.property("age");

151

expect(cleanObj.metadata).to.not.have.property("updated");

152

expect(cleanObj.name).to.equal("Alice");

153

expect(cleanObj.active).to.be.true();

154

155

// Handle arrays

156

const mixedArray = [1, "hello", new Date("2023-01-01"), undefined, null];

157

const cleanArray = toJSON(mixedArray);

158

expect(cleanArray).to.eql([1, "hello", "2023-01-01T00:00:00.000Z", null, null]);

159

160

// Use in API testing for comparing expected vs actual responses

161

const expectedResponse = {

162

id: 123,

163

name: "Test User",

164

createdAt: new Date("2023-01-01"),

165

metadata: undefined // This will be removed

166

};

167

168

const apiResponse = {

169

id: 123,

170

name: "Test User",

171

createdAt: "2023-01-01T00:00:00.000Z"

172

// No metadata property

173

};

174

175

// Compare using toJSON to normalize

176

expect(toJSON(expectedResponse)).to.eql(apiResponse);

177

178

// Handle nested objects and arrays

179

const complexData = {

180

users: [

181

{

182

id: 1,

183

name: "Alice",

184

lastLogin: new Date("2023-01-01"),

185

preferences: undefined

186

},

187

{

188

id: 2,

189

name: "Bob",

190

lastLogin: new Date("2023-01-02"),

191

preferences: {theme: "dark", notifications: true}

192

}

193

],

194

metadata: {

195

total: 2,

196

generated: new Date("2023-01-03"),

197

cached: undefined

198

}

199

};

200

201

const normalizedData = toJSON(complexData);

202

expect(normalizedData.users[0]).to.not.have.property("preferences");

203

expect(normalizedData.users[0].lastLogin).to.be.a.String();

204

expect(normalizedData.metadata).to.not.have.property("cached");

205

```

206

207

### Test Skipping Utilities

208

209

Helper functions for conditionally skipping tests based on various conditions.

210

211

```typescript { .api }

212

/**

213

* Generic type for test definition functions (it, describe, etc.)

214

*/

215

type TestDefinition<ARGS extends unknown[], RETVAL> = (

216

name: string,

217

...args: ARGS

218

) => RETVAL;

219

220

/**

221

* Helper function for skipping tests when a certain condition is met

222

* @param skip - Whether to skip the test

223

* @param verb - Test function (it, describe) with skip capability

224

* @param name - Test name

225

* @param args - Additional test arguments

226

* @returns Result of calling verb or verb.skip

227

*/

228

function skipIf<ARGS extends unknown[], RETVAL>(

229

skip: boolean,

230

verb: TestDefinition<ARGS, RETVAL> & {skip: TestDefinition<ARGS, RETVAL>},

231

name: string,

232

...args: ARGS

233

): RETVAL;

234

235

/**

236

* Helper function for skipping tests on Travis CI

237

* @param verb - Test function (it, describe) with skip capability

238

* @param name - Test name

239

* @param args - Additional test arguments

240

* @returns Result of calling verb or verb.skip if on Travis

241

*/

242

function skipOnTravis<ARGS extends unknown[], RETVAL>(

243

verb: TestDefinition<ARGS, RETVAL> & {skip: TestDefinition<ARGS, RETVAL>},

244

name: string,

245

...args: ARGS

246

): RETVAL;

247

```

248

249

**Usage Examples:**

250

251

```typescript

252

import { skipIf, skipOnTravis } from "@loopback/testlab";

253

254

// Skip based on feature flags

255

const features = {

256

freeFormProperties: process.env.ENABLE_FREEFORM === "true",

257

advancedAuth: process.env.ENABLE_ADVANCED_AUTH === "true",

258

experimentalFeatures: process.env.NODE_ENV === "development"

259

};

260

261

// Skip test suite if feature not enabled

262

skipIf(

263

!features.freeFormProperties,

264

describe,

265

"free-form properties (strict: false)",

266

() => {

267

it("should allow arbitrary properties", () => {

268

// Test implementation

269

});

270

271

it("should validate known properties", () => {

272

// Test implementation

273

});

274

}

275

);

276

277

// Skip individual test based on condition

278

skipIf(

279

!features.advancedAuth,

280

it,

281

"should support multi-factor authentication",

282

async () => {

283

// Test implementation for MFA

284

}

285

);

286

287

// Skip based on environment

288

skipIf(

289

process.platform === "win32",

290

it,

291

"should handle Unix file permissions",

292

() => {

293

// Unix-specific test

294

}

295

);

296

297

// Skip on Travis CI (for tests that don't work well in CI)

298

skipOnTravis(it, "should connect to external service", async () => {

299

// Test that requires external network access

300

});

301

302

skipOnTravis(describe, "Integration tests with external APIs", () => {

303

it("should fetch data from API", async () => {

304

// External API test

305

});

306

});

307

308

// Complex conditional skipping

309

const isCI = process.env.CI === "true";

310

const hasDockerAccess = process.env.DOCKER_AVAILABLE === "true";

311

312

skipIf(

313

isCI && !hasDockerAccess,

314

describe,

315

"Docker integration tests",

316

() => {

317

it("should start container", () => {

318

// Docker test

319

});

320

}

321

);

322

323

// Skip based on Node.js version

324

const nodeVersion = process.version;

325

const isOldNode = parseInt(nodeVersion.slice(1)) < 14;

326

327

skipIf(

328

isOldNode,

329

it,

330

"should use modern JavaScript features",

331

() => {

332

// Test using features from Node 14+

333

}

334

);

335

336

// Dynamic test generation with conditional skipping

337

const testCases = [

338

{name: "basic", condition: true},

339

{name: "advanced", condition: features.advancedAuth},

340

{name: "experimental", condition: features.experimentalFeatures}

341

];

342

343

testCases.forEach(testCase => {

344

skipIf(

345

!testCase.condition,

346

it,

347

`should handle ${testCase.name} scenario`,

348

() => {

349

// Test implementation

350

}

351

);

352

});

353

```

354

355

### Environment Detection and Helpers

356

357

Additional utilities for test environment detection and management.

358

359

**Usage Examples:**

360

361

```typescript

362

import { skipIf, skipOnTravis } from "@loopback/testlab";

363

364

// Environment detection helpers

365

const isCI = process.env.CI === "true";

366

const isTravis = process.env.TRAVIS === "true";

367

const isGitHubActions = process.env.GITHUB_ACTIONS === "true";

368

const isLocal = !isCI;

369

370

// Database availability

371

const hasDatabase = process.env.DATABASE_URL !== undefined;

372

const hasRedis = process.env.REDIS_URL !== undefined;

373

374

// External service availability

375

const hasS3Access = process.env.AWS_ACCESS_KEY_ID !== undefined;

376

const hasEmailService = process.env.SMTP_HOST !== undefined;

377

378

// Skip tests based on service availability

379

skipIf(!hasDatabase, describe, "Database integration tests", () => {

380

it("should connect to database", async () => {

381

// Database test

382

});

383

});

384

385

skipIf(!hasRedis, it, "should cache data in Redis", async () => {

386

// Redis test

387

});

388

389

// Skip flaky tests in CI

390

skipIf(isCI, it, "should handle timing-sensitive operations", async () => {

391

// Test that might be flaky in CI due to timing

392

});

393

394

// Skip tests requiring manual setup

395

skipIf(!hasS3Access, describe, "S3 file upload tests", () => {

396

it("should upload file to S3", async () => {

397

// S3 upload test

398

});

399

});

400

401

// Conditional test suites for different environments

402

if (isLocal) {

403

describe("Local development tests", () => {

404

it("should use development configuration", () => {

405

// Development-specific tests

406

});

407

});

408

}

409

410

if (isCI) {

411

describe("CI-specific tests", () => {

412

it("should use production-like configuration", () => {

413

// CI-specific tests

414

});

415

});

416

}

417

418

// Platform-specific tests

419

const testsByPlatform = {

420

linux: () => {

421

it("should handle Linux-specific features", () => {

422

// Linux test

423

});

424

},

425

darwin: () => {

426

it("should handle macOS-specific features", () => {

427

// macOS test

428

});

429

},

430

win32: () => {

431

it("should handle Windows-specific features", () => {

432

// Windows test

433

});

434

}

435

};

436

437

const currentPlatform = process.platform as keyof typeof testsByPlatform;

438

if (testsByPlatform[currentPlatform]) {

439

describe(`${currentPlatform} platform tests`, testsByPlatform[currentPlatform]);

440

}

441

442

// Version-based skipping

443

const nodeVersion = parseInt(process.version.slice(1));

444

skipIf(nodeVersion < 16, it, "should use Node 16+ features", () => {

445

// Modern Node.js features

446

});

447

448

// Memory and performance tests

449

const hasEnoughMemory = process.env.MEMORY_TEST === "true";

450

skipIf(!hasEnoughMemory, it, "should handle large datasets", () => {

451

// Memory-intensive test

452

});

453

454

// Integration with test frameworks

455

describe("Conditional test examples", () => {

456

// Standard test

457

it("should always run", () => {

458

expect(true).to.be.true();

459

});

460

461

// Conditionally skipped test

462

skipIf(

463

process.env.SKIP_SLOW_TESTS === "true",

464

it,

465

"should complete slow operation",

466

async function() {

467

this.timeout(30000); // 30 second timeout

468

// Slow test implementation

469

}

470

);

471

472

// Travis-specific skip

473

skipOnTravis(it, "should work with external dependencies", () => {

474

// Test that doesn't work well on Travis

475

});

476

});

477

```