A collection of test utilities specifically designed for LoopBack 4 applications and TypeScript testing
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
Shot-based HTTP request/response stubs for testing without running servers, including Express-specific context stubbing.
Shot library integration for HTTP request/response injection and mocking.
/**
* Shot injection function for simulating HTTP requests
* @param dispatchFunc - Function that handles the request
* @param options - Request configuration options
* @returns Promise resolving to response object
*/
function inject(
dispatchFunc: ShotListener,
options: ShotRequestOptions
): Promise<ResponseObject>;
/**
* Shot request options interface
*/
interface ShotRequestOptions {
url: string;
method?: string;
headers?: { [key: string]: string };
payload?: string | Buffer | object;
credentials?: any;
artifacts?: any;
app?: any;
plugins?: any;
validate?: boolean;
}
/**
* Shot listener function type
*/
type ShotListener = (req: IncomingMessage, res: ServerResponse) => void;Usage Examples:
import { inject } from "@loopback/testlab";
import { IncomingMessage, ServerResponse } from "http";
// Handler function to test
function handler(req: IncomingMessage, res: ServerResponse) {
res.writeHead(200, {"Content-Type": "application/json"});
res.end(JSON.stringify({url: req.url, method: req.method}));
}
// Inject request and get response
const response = await inject(handler, {
url: "/test",
method: "GET"
});
expect(response.statusCode).to.equal(200);
expect(JSON.parse(response.payload)).to.eql({
url: "/test",
method: "GET"
});
// POST request with payload
const postResponse = await inject(handler, {
url: "/users",
method: "POST",
payload: JSON.stringify({name: "Alice"}),
headers: {"Content-Type": "application/json"}
});Create stubbed HTTP server request objects for testing.
/**
* Creates a stubbed HTTP server request object
* @param options - Request configuration options
* @returns IncomingMessage stub with request properties
*/
function stubServerRequest(options: ShotRequestOptions): IncomingMessage;Usage Examples:
import { stubServerRequest, expect } from "@loopback/testlab";
// Create stubbed request
const request = stubServerRequest({
url: "/api/users?limit=10",
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer token123"
},
payload: JSON.stringify({name: "Alice", email: "alice@example.com"})
});
// Test request properties
expect(request.url).to.equal("/api/users?limit=10");
expect(request.method).to.equal("POST");
expect(request.headers["content-type"]).to.equal("application/json");
expect(request.headers.authorization).to.equal("Bearer token123");
// Use in handler testing
function authMiddleware(req: IncomingMessage, res: ServerResponse, next: Function) {
if (!req.headers.authorization) {
res.writeHead(401);
res.end("Unauthorized");
return;
}
next();
}
// Test middleware with stubbed request
const mockRes = { writeHead: sinon.stub(), end: sinon.stub() };
const next = sinon.stub();
authMiddleware(request, mockRes as any, next);
expect(next).to.have.been.called();Create stubbed HTTP server response objects for testing.
/**
* Creates a stubbed HTTP server response object
* @param request - Associated request object
* @param onEnd - Callback called when response ends
* @returns ServerResponse stub
*/
function stubServerResponse(
request: IncomingMessage,
onEnd: ShotCallback
): ServerResponse;
/**
* Callback function for Shot response completion
*/
type ShotCallback = (response: ResponseObject) => void;
/**
* Shot response constructor type
*/
type ShotResponseCtor = new (
request: IncomingMessage,
onEnd: ShotCallback
) => ServerResponse;
/**
* Observed response type (alias for ResponseObject)
*/
type ObservedResponse = ResponseObject;Usage Examples:
import { stubServerRequest, stubServerResponse, expect } from "@loopback/testlab";
// Create request and response stubs
const request = stubServerRequest({url: "/test"});
let capturedResponse: any;
const response = stubServerResponse(request, (res) => {
capturedResponse = res;
});
// Test response operations
response.writeHead(200, {"Content-Type": "application/json"});
response.write(JSON.stringify({message: "Hello"}));
response.end();
// Verify captured response
expect(capturedResponse.statusCode).to.equal(200);
expect(capturedResponse.headers["content-type"]).to.equal("application/json");
expect(JSON.parse(capturedResponse.payload)).to.eql({message: "Hello"});Create complete handler context stubs with request, response, and result promise.
/**
* Creates a stubbed handler context for testing
* @param requestOptions - Optional request configuration
* @returns Handler context stub with request, response, and result promise
*/
function stubHandlerContext(
requestOptions?: ShotRequestOptions
): HandlerContextStub;
/**
* Handler context stub interface
*/
interface HandlerContextStub {
request: IncomingMessage;
response: ServerResponse;
result: Promise<ObservedResponse>;
}Usage Examples:
import { stubHandlerContext, expect } from "@loopback/testlab";
// Create handler context
const context = stubHandlerContext({
url: "/api/test",
method: "GET"
});
// Use in handler
function testHandler(req: IncomingMessage, res: ServerResponse) {
res.writeHead(200, {"Content-Type": "text/plain"});
res.end("Hello World");
}
// Execute handler
testHandler(context.request, context.response);
// Wait for result
const result = await context.result;
expect(result.statusCode).to.equal(200);
expect(result.payload).to.equal("Hello World");
expect(result.headers["content-type"]).to.equal("text/plain");
// Test async handler
async function asyncHandler(req: IncomingMessage, res: ServerResponse) {
// Simulate async operation
await new Promise(resolve => setTimeout(resolve, 10));
res.writeHead(200);
res.end("Async response");
}
const asyncContext = stubHandlerContext();
asyncHandler(asyncContext.request, asyncContext.response);
const asyncResult = await asyncContext.result;
expect(asyncResult.payload).to.equal("Async response");Create Express-specific context stubs with full Express request/response API.
/**
* Creates a stubbed Express context for testing Express applications
* @param requestOptions - Optional request configuration
* @returns Express context stub with app, request, response, and result
*/
function stubExpressContext(
requestOptions?: ShotRequestOptions
): ExpressContextStub;
/**
* Express context stub interface
*/
interface ExpressContextStub extends HandlerContextStub {
app: express.Application;
request: express.Request;
response: express.Response;
result: Promise<ObservedResponse>;
}Usage Examples:
import { stubExpressContext, expect } from "@loopback/testlab";
import express from "express";
// Create Express context
const context = stubExpressContext({
url: "/users/123?include=profile",
method: "GET",
headers: {"Accept": "application/json"}
});
// Test Express request properties
expect(context.request.params).to.be.an.Object();
expect(context.request.query).to.eql({include: "profile"});
expect(context.request.get("Accept")).to.equal("application/json");
expect(context.app).to.be.an.instanceOf(express.application.constructor);
// Use with Express middleware
function parseUserId(req: express.Request, res: express.Response, next: express.NextFunction) {
const userId = req.url.match(/\/users\/(\d+)/)?.[1];
if (userId) {
(req as any).userId = userId;
next();
} else {
res.status(400).json({error: "Invalid user ID"});
}
}
// Test middleware
const next = sinon.stub();
parseUserId(context.request, context.response, next);
expect((context.request as any).userId).to.equal("123");
expect(next).to.have.been.called();
// Use with Express route handler
function getUserHandler(req: express.Request, res: express.Response) {
const userId = (req as any).userId;
const includeProfile = req.query.include === "profile";
res.json({
id: userId,
name: "Alice",
...(includeProfile && {profile: {age: 30}})
});
}
getUserHandler(context.request, context.response);
const result = await context.result;
expect(result.statusCode).to.equal(200);
expect(JSON.parse(result.payload)).to.eql({
id: "123",
name: "Alice",
profile: {age: 30}
});Complex testing scenarios and patterns.
Usage Examples:
import {
stubExpressContext,
stubHandlerContext,
inject,
expect,
sinon
} from "@loopback/testlab";
// Testing middleware chains
async function testMiddlewareChain() {
const context = stubExpressContext({
url: "/protected",
headers: {"Authorization": "Bearer valid-token"}
});
const middlewares = [authMiddleware, validateToken, handler];
// Simulate middleware chain
for (const middleware of middlewares) {
await new Promise((resolve) => {
middleware(context.request, context.response, resolve);
});
}
const result = await context.result;
expect(result.statusCode).to.equal(200);
}
// Testing error handling
async function testErrorHandling() {
const context = stubHandlerContext();
function errorHandler(req: IncomingMessage, res: ServerResponse) {
try {
throw new Error("Something went wrong");
} catch (error) {
res.writeHead(500);
res.end(JSON.stringify({error: error.message}));
}
}
errorHandler(context.request, context.response);
const result = await context.result;
expect(result.statusCode).to.equal(500);
expect(JSON.parse(result.payload)).to.eql({
error: "Something went wrong"
});
}
// Testing with POST data
async function testPostData() {
const postData = {name: "Bob", email: "bob@example.com"};
const context = stubExpressContext({
url: "/users",
method: "POST",
payload: JSON.stringify(postData),
headers: {"Content-Type": "application/json"}
});
function createUserHandler(req: express.Request, res: express.Response) {
// In real Express app, body would be parsed by body-parser middleware
// For testing, we can access the raw payload
const body = JSON.parse((req as any).payload || "{}");
res.status(201).json({
id: Math.random().toString(36),
...body,
createdAt: new Date().toISOString()
});
}
createUserHandler(context.request, context.response);
const result = await context.result;
expect(result.statusCode).to.equal(201);
const responseBody = JSON.parse(result.payload);
expect(responseBody).to.have.property("id");
expect(responseBody.name).to.equal("Bob");
expect(responseBody.email).to.equal("bob@example.com");
}