After Response Hooks
After Response hooks run after an API request has completed and a response has been received. You can use these hooks to process the response, perform assertions, and update the test context with data from the response.
Multi-Level Hook System
After Response hooks in ReAPI follow the same 3-level hierarchy as Before Request hooks, but with reverse execution order to form a symmetric “onion model”:
- Node Level - Individual API response processing
- Folder Level - Module/feature-specific processing (with folder hierarchy)
- Test Flow Level - Global response handling for the entire test case
Each level supports two modes:
- Inline: Write hook code directly at that level
- Reference: Select a reusable hook function by ID (defined in Global Scripts)
Execution Order: Inner → Outer (REVERSED)
After Response hooks execute from inner to outer (specific to general), forming the symmetric counterpart to Before Request:
← API Response Received
↓
Node Referenced Hooks (reusable functions)
↓
Node Inline Hook (specific to this API)
↓
Folder Hooks: Current → Parent → Root (module logic - REVERSED)
↓
Test Flow Hooks (global cleanup/tracking)Why this reverse order?
- Start with endpoint-specific extraction (get data needed from this response)
- Add module-level processing (decrypt, aggregate module data)
- Finish with global cleanup/tracking (statistics, logging)
- Like unwrapping layers of an onion from inside out
Symmetric Onion Pattern
Before Request (Build Up): After Response (Unwrap):
┌─────────────────────────────────┐ ┌─────────────────────────────────┐
│ Test Flow: Auth, Debug │ │ Test Flow: Cleanup, Stats │
│ ┌───────────────────────────┐ │ │ ┌───────────────────────────┐ │
│ │ Folder: Module Headers │ │ │ │ Folder: Decrypt, Aggregate│ │
│ │ ┌─────────────────────┐ │ │ │ │ ┌─────────────────────┐ │ │
│ │ │ Node: Specific Params│ │ │ │ │ │ Node: Extract Data │ │ │
│ │ │ → API Call → │ │ │ │ │ │ ← API Response ← │ │ │
│ │ └─────────────────────┘ │ │ │ │ └─────────────────────┘ │ │
│ └───────────────────────────┘ │ │ └───────────────────────────┘ │
└─────────────────────────────────┘ └─────────────────────────────────┘Execution Order Example
// Node Inline Hook: Extract specific data first
async function afterResponse() {
$context.refundId = $response.data.refundId;
$context.transactionId = $response.data.transactionId;
}
// Folder Hook (Payment Module): Decrypt and aggregate
async function afterResponse() {
if ($response.data.encryptedCard) {
$response.data.decryptedCard = $$PaymentUtils.decrypt(
$response.data.encryptedCard
);
}
$context.paymentStats = $context.paymentStats || { total: 0, approved: 0 };
$context.paymentStats.total += 1;
if ($response.data.status === "approved") {
$context.paymentStats.approved += 1;
}
}
// Test Flow Hook: Track global statistics
async function afterResponse() {
$context.apiCallLog = $context.apiCallLog || [];
$context.apiCallLog.push({
url: $request.url,
status: $response.status,
time: $response.time,
});
}Result: Data extracted → Module processed → Global tracked—perfect unwrapping from inner to outer.
Script Pattern
An After Response hook must define an async function named afterResponse. This function has access to $request (read-only), $response, and $context.
async function afterResponse() {
// Access response data
const statusCode = $response.status;
const responseData = $response.data;
// Modify response data before it's passed to subsequent steps
$response.data.processedAt = new Date().toISOString();
// Update context with response information
$context.lastResponseStatus = $response.status;
if (responseData.token) {
$context.token = responseData.token;
}
// Add assertions using Chai
$expect($response.status).to.equal(200);
$assert.isObject($response.data);
}Key Points
- Function Name: Must be
afterResponse. - Async: Must be an
asyncfunction. - Response Access: Use
$responseto read and modify the HTTP response. - Request Access: Use
$requestfor read-only access to the original request. - Context Access: Use
$contextto read and write to the test context. - Assertions: Use the built-in
$assertand$expect(Chai) to validate the response.
Available Response Properties
$response.status: HTTP status code$response.data: Response body$response.headers: Response headers$response.statusText: HTTP status text
Example: Response Validation and Data Extraction
async function afterResponse() {
// Validate response structure
$expect($response.status).to.be.oneOf([200, 201]);
$assert.isObject($response.data, "Response should be an object");
// Extract data for subsequent requests
if ($response.data.sessionToken) {
$context.sessionToken = $response.data.sessionToken;
}
// Track metrics
$context.lastApiCall = {
url: $request.url,
status: $response.status,
duration: Date.now() - $context.requestStartTime,
};
}When to Use Each Level
| Level | Best For | Examples |
|---|---|---|
| Node | Endpoint-specific data extraction | Extract specific IDs, decrypt endpoint data, endpoint-specific assertions |
| Folder | Module/feature-specific processing | Decrypt module data, aggregate stats, module validation |
| Test Flow | Global tracking and cleanup | Global statistics, request logging, cleanup tasks |
Why Reverse Order Matters
The reverse execution order (inner → outer) allows each layer to access and enhance what inner layers produced:
// Node Hook: Extract raw data
async function afterResponse() {
$context.orderId = $response.data.orderId;
$context.rawOrderData = $response.data;
}
// Folder Hook: Process that extracted data
async function afterResponse() {
// Can access $context.orderId set by node hook
const order = $context.rawOrderData;
$context.processedOrder = $$OrderUtils.transformOrder(order);
}
// Test Flow Hook: Use processed data for global tracking
async function afterResponse() {
// Can access $context.processedOrder set by folder hook
$context.allOrders = $context.allOrders || [];
$context.allOrders.push($context.processedOrder);
}Reusable Hooks vs Inline Hooks
Reusable Hooks (Recommended for Common Logic)
Define once in Global Scripts:
// Global Script
async function trackResponseMetrics() {
$context.apiCallLog = $context.apiCallLog || [];
$context.apiCallLog.push({
url: $request.url,
status: $response.status,
time: $response.time,
timestamp: Date.now(),
});
}
async function extractCommonData() {
if ($response.data.token) {
$context.token = $response.data.token;
}
if ($response.data.userId) {
$context.userId = $response.data.userId;
}
}Reference by ID in UI: QA selects “trackResponseMetrics” from dropdown at Test Flow level—applies to all API responses.
Benefits:
- ✅ Write once, use everywhere
- ✅ Consistent data extraction across all tests
- ✅ QA can apply via UI without coding
Inline Hooks (For Specific Use Cases)
Write directly at Node level:
// Inline at Node level for a specific endpoint response
async function afterResponse() {
// This specific endpoint returns encrypted audit trail
if ($response.data.encryptedAuditTrail) {
$response.data.auditTrail = $$PaymentUtils.decryptAuditTrail(
$response.data.encryptedAuditTrail
);
}
}Benefits:
- ✅ Quick one-off response processing
- ✅ Endpoint-specific logic stays with the endpoint
- ✅ No need to create global function
Best Practices
1. Use Node Hooks for Specific Data Extraction
// ✅ Good: Extract exactly what this endpoint returns
async function afterResponse() {
$context.paymentId = $response.data.paymentId;
$context.transactionStatus = $response.data.status;
$context.receiptUrl = $response.data.receiptUrl;
}2. Use Folder Hooks for Module-Level Processing
// ✅ Good: Decrypt and aggregate for payment module
async function afterResponse() {
// Decrypt sensitive payment data
if ($response.data.encryptedCardLast4) {
$response.data.cardLast4 = $$PaymentUtils.decrypt(
$response.data.encryptedCardLast4
);
}
// Aggregate payment statistics
$context.paymentStats = $context.paymentStats || { total: 0, successful: 0 };
$context.paymentStats.total += 1;
if ($response.data.status === "approved") {
$context.paymentStats.successful += 1;
}
}3. Use Test Flow Hooks for Global Tracking
// ✅ Good: Track all API calls across entire test
async function afterResponse() {
$context.totalApiCalls = ($context.totalApiCalls || 0) + 1;
$context.totalResponseTime =
($context.totalResponseTime || 0) + $response.time;
$context.apiCallLog = $context.apiCallLog || [];
$context.apiCallLog.push({
url: $request.url,
method: $request.method,
status: $response.status,
time: $response.time,
});
}4. Leverage Global Utilities for Complex Processing
// ✅ Good: Use global utilities for complex transformations
async function afterResponse() {
// Validate response with Zod schema
const parseResult = $$Schemas.apiResponse.safeParse($response.data);
if (!parseResult.success) {
$context.validationErrors = parseResult.error.errors;
return;
}
// Transform timestamps to readable format
$response.data.createdAtFormatted = $$DateUtils.formatTimestamp(
$response.data.createdAt
);
// Generate data integrity hash
$response.data.hash = $$DataUtils.generateHash($response.data);
}5. Use Assertions Appropriately by Level
// ✅ Node Level: Endpoint-specific assertions
async function afterResponse() {
$expect($response.status).to.equal(201);
$expect($response.data.orderId).to.be.a("string");
}
// ✅ Folder Level: Module business rules
async function afterResponse() {
// Payment module: ensure fraud check was performed
$expect($response.data.fraudCheckStatus).to.exist;
$expect($response.data.fraudScore).to.be.within(0, 100);
}
// ✅ Test Flow Level: Global assertions
async function afterResponse() {
// All APIs should return within SLA
$expect($response.time).to.be.lessThan($context.config.slaThreshold);
}6. Transform Data Progressively
// Node Hook: Extract raw data
async function afterResponse() {
$context.rawMetrics = $response.data.metrics;
}
// Folder Hook: Transform for module needs
async function afterResponse() {
$context.processedMetrics = $$AnalyticsUtils.processMetrics(
$context.rawMetrics
);
}
// Test Flow Hook: Aggregate across all API calls
async function afterResponse() {
$context.allMetrics = $context.allMetrics || [];
$context.allMetrics.push(...$context.processedMetrics);
}Key Takeaways
- Reverse Onion Pattern - Node → Folder → Test Flow (inner to outer)
- Symmetric with Before Request - Build up request, unwrap response
- Each Layer Enhances - Inner layers extract, outer layers aggregate
- Inline or Reference - Write directly or select from global functions
- Use Assertions at All Levels - Specific → Module → Global
- Progressive Transformation - Extract → Process → Aggregate
- Leverage Utilities - Call
$$Utilsfor complex operations
For a comprehensive example of the multi-level hook system, see Build a Testing Framework.