0
# Request/Response Mocking
1
2
Shot-based HTTP request/response stubs for testing without running servers, including Express-specific context stubbing.
3
4
## Capabilities
5
6
### Shot Integration
7
8
Shot library integration for HTTP request/response injection and mocking.
9
10
```typescript { .api }
11
/**
12
* Shot injection function for simulating HTTP requests
13
* @param dispatchFunc - Function that handles the request
14
* @param options - Request configuration options
15
* @returns Promise resolving to response object
16
*/
17
function inject(
18
dispatchFunc: ShotListener,
19
options: ShotRequestOptions
20
): Promise<ResponseObject>;
21
22
/**
23
* Shot request options interface
24
*/
25
interface ShotRequestOptions {
26
url: string;
27
method?: string;
28
headers?: { [key: string]: string };
29
payload?: string | Buffer | object;
30
credentials?: any;
31
artifacts?: any;
32
app?: any;
33
plugins?: any;
34
validate?: boolean;
35
}
36
37
/**
38
* Shot listener function type
39
*/
40
type ShotListener = (req: IncomingMessage, res: ServerResponse) => void;
41
```
42
43
**Usage Examples:**
44
45
```typescript
46
import { inject } from "@loopback/testlab";
47
import { IncomingMessage, ServerResponse } from "http";
48
49
// Handler function to test
50
function handler(req: IncomingMessage, res: ServerResponse) {
51
res.writeHead(200, {"Content-Type": "application/json"});
52
res.end(JSON.stringify({url: req.url, method: req.method}));
53
}
54
55
// Inject request and get response
56
const response = await inject(handler, {
57
url: "/test",
58
method: "GET"
59
});
60
61
expect(response.statusCode).to.equal(200);
62
expect(JSON.parse(response.payload)).to.eql({
63
url: "/test",
64
method: "GET"
65
});
66
67
// POST request with payload
68
const postResponse = await inject(handler, {
69
url: "/users",
70
method: "POST",
71
payload: JSON.stringify({name: "Alice"}),
72
headers: {"Content-Type": "application/json"}
73
});
74
```
75
76
### Server Request Stubbing
77
78
Create stubbed HTTP server request objects for testing.
79
80
```typescript { .api }
81
/**
82
* Creates a stubbed HTTP server request object
83
* @param options - Request configuration options
84
* @returns IncomingMessage stub with request properties
85
*/
86
function stubServerRequest(options: ShotRequestOptions): IncomingMessage;
87
```
88
89
**Usage Examples:**
90
91
```typescript
92
import { stubServerRequest, expect } from "@loopback/testlab";
93
94
// Create stubbed request
95
const request = stubServerRequest({
96
url: "/api/users?limit=10",
97
method: "POST",
98
headers: {
99
"Content-Type": "application/json",
100
"Authorization": "Bearer token123"
101
},
102
payload: JSON.stringify({name: "Alice", email: "alice@example.com"})
103
});
104
105
// Test request properties
106
expect(request.url).to.equal("/api/users?limit=10");
107
expect(request.method).to.equal("POST");
108
expect(request.headers["content-type"]).to.equal("application/json");
109
expect(request.headers.authorization).to.equal("Bearer token123");
110
111
// Use in handler testing
112
function authMiddleware(req: IncomingMessage, res: ServerResponse, next: Function) {
113
if (!req.headers.authorization) {
114
res.writeHead(401);
115
res.end("Unauthorized");
116
return;
117
}
118
next();
119
}
120
121
// Test middleware with stubbed request
122
const mockRes = { writeHead: sinon.stub(), end: sinon.stub() };
123
const next = sinon.stub();
124
125
authMiddleware(request, mockRes as any, next);
126
expect(next).to.have.been.called();
127
```
128
129
### Server Response Stubbing
130
131
Create stubbed HTTP server response objects for testing.
132
133
```typescript { .api }
134
/**
135
* Creates a stubbed HTTP server response object
136
* @param request - Associated request object
137
* @param onEnd - Callback called when response ends
138
* @returns ServerResponse stub
139
*/
140
function stubServerResponse(
141
request: IncomingMessage,
142
onEnd: ShotCallback
143
): ServerResponse;
144
145
/**
146
* Callback function for Shot response completion
147
*/
148
type ShotCallback = (response: ResponseObject) => void;
149
150
/**
151
* Shot response constructor type
152
*/
153
type ShotResponseCtor = new (
154
request: IncomingMessage,
155
onEnd: ShotCallback
156
) => ServerResponse;
157
158
/**
159
* Observed response type (alias for ResponseObject)
160
*/
161
type ObservedResponse = ResponseObject;
162
```
163
164
**Usage Examples:**
165
166
```typescript
167
import { stubServerRequest, stubServerResponse, expect } from "@loopback/testlab";
168
169
// Create request and response stubs
170
const request = stubServerRequest({url: "/test"});
171
let capturedResponse: any;
172
173
const response = stubServerResponse(request, (res) => {
174
capturedResponse = res;
175
});
176
177
// Test response operations
178
response.writeHead(200, {"Content-Type": "application/json"});
179
response.write(JSON.stringify({message: "Hello"}));
180
response.end();
181
182
// Verify captured response
183
expect(capturedResponse.statusCode).to.equal(200);
184
expect(capturedResponse.headers["content-type"]).to.equal("application/json");
185
expect(JSON.parse(capturedResponse.payload)).to.eql({message: "Hello"});
186
```
187
188
### Handler Context Stubbing
189
190
Create complete handler context stubs with request, response, and result promise.
191
192
```typescript { .api }
193
/**
194
* Creates a stubbed handler context for testing
195
* @param requestOptions - Optional request configuration
196
* @returns Handler context stub with request, response, and result promise
197
*/
198
function stubHandlerContext(
199
requestOptions?: ShotRequestOptions
200
): HandlerContextStub;
201
202
/**
203
* Handler context stub interface
204
*/
205
interface HandlerContextStub {
206
request: IncomingMessage;
207
response: ServerResponse;
208
result: Promise<ObservedResponse>;
209
}
210
```
211
212
**Usage Examples:**
213
214
```typescript
215
import { stubHandlerContext, expect } from "@loopback/testlab";
216
217
// Create handler context
218
const context = stubHandlerContext({
219
url: "/api/test",
220
method: "GET"
221
});
222
223
// Use in handler
224
function testHandler(req: IncomingMessage, res: ServerResponse) {
225
res.writeHead(200, {"Content-Type": "text/plain"});
226
res.end("Hello World");
227
}
228
229
// Execute handler
230
testHandler(context.request, context.response);
231
232
// Wait for result
233
const result = await context.result;
234
expect(result.statusCode).to.equal(200);
235
expect(result.payload).to.equal("Hello World");
236
expect(result.headers["content-type"]).to.equal("text/plain");
237
238
// Test async handler
239
async function asyncHandler(req: IncomingMessage, res: ServerResponse) {
240
// Simulate async operation
241
await new Promise(resolve => setTimeout(resolve, 10));
242
res.writeHead(200);
243
res.end("Async response");
244
}
245
246
const asyncContext = stubHandlerContext();
247
asyncHandler(asyncContext.request, asyncContext.response);
248
const asyncResult = await asyncContext.result;
249
expect(asyncResult.payload).to.equal("Async response");
250
```
251
252
### Express Context Stubbing
253
254
Create Express-specific context stubs with full Express request/response API.
255
256
```typescript { .api }
257
/**
258
* Creates a stubbed Express context for testing Express applications
259
* @param requestOptions - Optional request configuration
260
* @returns Express context stub with app, request, response, and result
261
*/
262
function stubExpressContext(
263
requestOptions?: ShotRequestOptions
264
): ExpressContextStub;
265
266
/**
267
* Express context stub interface
268
*/
269
interface ExpressContextStub extends HandlerContextStub {
270
app: express.Application;
271
request: express.Request;
272
response: express.Response;
273
result: Promise<ObservedResponse>;
274
}
275
```
276
277
**Usage Examples:**
278
279
```typescript
280
import { stubExpressContext, expect } from "@loopback/testlab";
281
import express from "express";
282
283
// Create Express context
284
const context = stubExpressContext({
285
url: "/users/123?include=profile",
286
method: "GET",
287
headers: {"Accept": "application/json"}
288
});
289
290
// Test Express request properties
291
expect(context.request.params).to.be.an.Object();
292
expect(context.request.query).to.eql({include: "profile"});
293
expect(context.request.get("Accept")).to.equal("application/json");
294
expect(context.app).to.be.an.instanceOf(express.application.constructor);
295
296
// Use with Express middleware
297
function parseUserId(req: express.Request, res: express.Response, next: express.NextFunction) {
298
const userId = req.url.match(/\/users\/(\d+)/)?.[1];
299
if (userId) {
300
(req as any).userId = userId;
301
next();
302
} else {
303
res.status(400).json({error: "Invalid user ID"});
304
}
305
}
306
307
// Test middleware
308
const next = sinon.stub();
309
parseUserId(context.request, context.response, next);
310
expect((context.request as any).userId).to.equal("123");
311
expect(next).to.have.been.called();
312
313
// Use with Express route handler
314
function getUserHandler(req: express.Request, res: express.Response) {
315
const userId = (req as any).userId;
316
const includeProfile = req.query.include === "profile";
317
318
res.json({
319
id: userId,
320
name: "Alice",
321
...(includeProfile && {profile: {age: 30}})
322
});
323
}
324
325
getUserHandler(context.request, context.response);
326
327
const result = await context.result;
328
expect(result.statusCode).to.equal(200);
329
expect(JSON.parse(result.payload)).to.eql({
330
id: "123",
331
name: "Alice",
332
profile: {age: 30}
333
});
334
```
335
336
### Advanced Usage Patterns
337
338
Complex testing scenarios and patterns.
339
340
**Usage Examples:**
341
342
```typescript
343
import {
344
stubExpressContext,
345
stubHandlerContext,
346
inject,
347
expect,
348
sinon
349
} from "@loopback/testlab";
350
351
// Testing middleware chains
352
async function testMiddlewareChain() {
353
const context = stubExpressContext({
354
url: "/protected",
355
headers: {"Authorization": "Bearer valid-token"}
356
});
357
358
const middlewares = [authMiddleware, validateToken, handler];
359
360
// Simulate middleware chain
361
for (const middleware of middlewares) {
362
await new Promise((resolve) => {
363
middleware(context.request, context.response, resolve);
364
});
365
}
366
367
const result = await context.result;
368
expect(result.statusCode).to.equal(200);
369
}
370
371
// Testing error handling
372
async function testErrorHandling() {
373
const context = stubHandlerContext();
374
375
function errorHandler(req: IncomingMessage, res: ServerResponse) {
376
try {
377
throw new Error("Something went wrong");
378
} catch (error) {
379
res.writeHead(500);
380
res.end(JSON.stringify({error: error.message}));
381
}
382
}
383
384
errorHandler(context.request, context.response);
385
386
const result = await context.result;
387
expect(result.statusCode).to.equal(500);
388
expect(JSON.parse(result.payload)).to.eql({
389
error: "Something went wrong"
390
});
391
}
392
393
// Testing with POST data
394
async function testPostData() {
395
const postData = {name: "Bob", email: "bob@example.com"};
396
const context = stubExpressContext({
397
url: "/users",
398
method: "POST",
399
payload: JSON.stringify(postData),
400
headers: {"Content-Type": "application/json"}
401
});
402
403
function createUserHandler(req: express.Request, res: express.Response) {
404
// In real Express app, body would be parsed by body-parser middleware
405
// For testing, we can access the raw payload
406
const body = JSON.parse((req as any).payload || "{}");
407
408
res.status(201).json({
409
id: Math.random().toString(36),
410
...body,
411
createdAt: new Date().toISOString()
412
});
413
}
414
415
createUserHandler(context.request, context.response);
416
417
const result = await context.result;
418
expect(result.statusCode).to.equal(201);
419
const responseBody = JSON.parse(result.payload);
420
expect(responseBody).to.have.property("id");
421
expect(responseBody.name).to.equal("Bob");
422
expect(responseBody.email).to.equal("bob@example.com");
423
}
424
```