Value Generator Functions
Value Generators allow you to create dynamic, reusable data for your tests. They’re a key part of ReAPI’s empowerment model: developers write complex generation logic once, and QA uses it everywhere via simple function calls.
What Are Value Generators?
Value Generators are functions that:
- Generate dynamic test data (IDs, timestamps, complex objects)
- Fetch data from external sources (test data APIs)
- Transform data based on parameters
- Encapsulate complex data creation logic
Key advantages:
- ✅ Reusable - Write once, call hundreds of times
- ✅ Parameterized - Flexible configuration via function parameters
- ✅ UI-accessible - QA can configure and use via no-code UI
- ✅ Testable - Built-in testing capability before activation
Two Ways to Use Generators
1. No-Code UI (Recommended for QA)
QA can use generators directly in the UI without writing any code:
- Select generator from dropdown
- Configure parameters via form fields
- Preview generated value
- Use in test node
2. JavaScript Expressions (For Dynamic Scenarios)
Call generators from JS expressions for more flexibility:
// In expression field:
js: $gen.generateUserId();
js: $gen.createTestUser({ role: "admin", verified: true });
js: $gen.calculateOrderTotal($context.order, { includeShipping: true });Script Pattern
A Value Generator is a JavaScript function that returns a value. The function can accept zero or one parameter.
// No parameter
function getCurrentTimestamp() {
return Date.now();
}
// Simple parameter (e.g., a number)
async function generateUsers(count) {
const users = [];
for (let i = 0; i < count; i++) {
users.push({
id: i + 1,
name: `User ${i + 1}`,
});
}
return users;
}
// Complex parameter (e.g., an object)
async function generateTestData(options) {
const { type, count = 10 } = options || {};
if (type === "users") {
return Array.from({ length: count }, (_, i) => ({
id: i + 1,
name: `User ${i + 1}`,
}));
}
return { message: "Unknown type" };
}Key Points
- Function Name: Can be any valid JavaScript function name (not fixed like hooks)
- Important: The function name must match the title you give it in the UI
- Example: UI title “Generate Test User” → function name
generateTestUser
- Parameters: The function can accept zero or one parameter
- No parameter:
function getTimestamp() - Single primitive:
function createUsers(count) - Single object:
function createUser(options)← Recommended for complex parameters
- No parameter:
- Return Value: The function must return a value (can be any type)
- Async/Sync: The function can be either
asyncor synchronous - Context Access: You can read from
$contextif needed - Parameter Validation: Use Zod to validate and parse parameters
Parameter Examples
// Called with a primitive parameter: 5
async function createUsers(count) {
// count will be 5
return Array.from({ length: count }, () => ({
/* ... */
}));
}
// Called with an object parameter: { type: 'admin', count: 3 }
async function createUsers(options) {
// options will be { type: 'admin', count: 3 }
const { type, count } = options;
return Array.from({ length: count }, () => ({ type /* ... */ }));
}
// Called with no parameter
function getDefaultConfig() {
return { timeout: 30000, retries: 3 };
}Parameter Validation with Zod
Best practice: Always validate complex parameters with Zod to ensure type safety and provide clear error messages.
Why Validate Parameters?
- ✅ Type safety - Catch invalid parameters early
- ✅ Default values - Automatically apply defaults
- ✅ Clear errors - Zod provides descriptive error messages
- ✅ Self-documenting - Schema serves as parameter documentation
Pattern: Validate with parse()
Use Zod’s parse() method to validate and throw errors automatically:
import { z } from "zod";
// Define schema
const UserSchema = z.object({
role: z.enum(["admin", "user", "guest"]).default("user"),
verified: z.boolean().default(false),
count: z.number().int().positive().default(1),
});
async function generateTestUsers(options) {
// Validate and parse parameters (throws if invalid)
const config = UserSchema.parse(options || {});
const users = [];
for (let i = 0; i < config.count; i++) {
users.push({
id: faker.number.int({ min: 1000, max: 9999 }),
email: faker.internet.email(),
role: config.role,
verified: config.verified,
createdAt: Date.now(),
});
}
return users;
}What happens:
- Valid parameters: Function continues with validated data
- Invalid parameters: Zod throws descriptive error, UI shows it to user
- Missing parameters: Zod applies defaults automatically
Example: Complex Generator with Validation
import { z } from "zod";
// Define reusable schema in Global Scripts for consistency
class $$GeneratorSchemas {
static tenantMetrics = z.object({
count: z
.number()
.int()
.positive()
.default(10)
.describe("Number of metrics to generate"),
min: z.number().default(0).describe("Minimum metric value"),
max: z.number().default(1000).describe("Maximum metric value"),
unit: z.string().default("requests").describe("Metric unit"),
includeHash: z
.boolean()
.default(true)
.describe("Include data integrity hash"),
});
}
async function generateTenantMetrics(options) {
// Validate parameters
const config = $$GeneratorSchemas.tenantMetrics.parse(options || {});
const metrics = [];
for (let i = 0; i < config.count; i++) {
const value = _.random(config.min, config.max);
const metric = {
id: faker.string.uuid(),
value,
unit: config.unit,
timestamp: Date.now(),
tenantId: $context.tenantId,
};
if (config.includeHash) {
metric.hash = $$AnalyticsUtils.generateDataHash(metric);
}
metrics.push(metric);
}
return metrics;
}Common Patterns
Pattern 1: Simple Data Generation
// No parameter - generate unique ID
function generateUserId() {
return `user_${Date.now()}_${_.random(1000, 9999)}`;
}
// No parameter - current timestamp
function getCurrentTimestamp() {
return Date.now();
}
// No parameter - random test email
function generateTestEmail() {
return faker.internet.email();
}Pattern 2: Parameterized Data Generation
import { z } from "zod";
const OrderSchema = z.object({
itemCount: z.number().int().positive().default(3),
currency: z.string().default("USD"),
includeShipping: z.boolean().default(true),
});
async function generateTestOrder(options) {
const config = OrderSchema.parse(options || {});
const items = [];
let subtotal = 0;
for (let i = 0; i < config.itemCount; i++) {
const price = _.random(10, 200);
const quantity = _.random(1, 5);
items.push({
id: faker.string.uuid(),
name: faker.commerce.productName(),
price,
quantity,
});
subtotal += price * quantity;
}
const shipping = config.includeShipping ? 10 : 0;
return {
id: faker.string.uuid(),
items,
subtotal,
shipping,
total: subtotal + shipping,
currency: config.currency,
createdAt: Date.now(),
};
}Pattern 3: Fetch Remote Data
import { z } from "zod";
const UserCriteriaSchema = z.object({
role: z.enum(["admin", "user", "guest"]).optional(),
verified: z.boolean().optional(),
country: z.string().optional(),
});
async function fetchTestUser(criteria) {
const config = UserCriteriaSchema.parse(criteria || {});
// Build query parameters
const params = new URLSearchParams();
if (config.role) params.append("role", config.role);
if (config.verified !== undefined) params.append("verified", config.verified);
if (config.country) params.append("country", config.country);
// Fetch from test data API
const baseUrl =
$context.config.testDataApiUrl || "https://test-data.example.com";
const response = await ky
.get(`${baseUrl}/users?${params}`, {
timeout: 10000,
})
.json();
return response.data;
}Pattern 4: Transform Existing Data
import { z } from "zod";
const TransformSchema = z.object({
includeShipping: z.boolean().default(true),
taxRate: z.number().min(0).max(1).default(0.2),
discountPercent: z.number().min(0).max(100).default(0),
});
async function calculateOrderTotal(order, options) {
const config = TransformSchema.parse(options || {});
// Calculate subtotal
const subtotal = order.items.reduce((sum, item) => {
return sum + item.price * item.quantity;
}, 0);
// Apply discount
const discount = subtotal * (config.discountPercent / 100);
const afterDiscount = subtotal - discount;
// Apply tax
const tax = afterDiscount * config.taxRate;
// Add shipping
const shipping = config.includeShipping ? order.shipping || 10 : 0;
return {
subtotal,
discount,
tax,
shipping,
total: afterDiscount + tax + shipping,
};
}Pattern 5: Leverage Global Utilities
import { z } from "zod";
const MetricSchema = z.object({
value: z.number(),
unit: z.string(),
slaThreshold: z.number().optional(),
});
async function generateValidatedMetric(options) {
const config = MetricSchema.parse(options || {});
const metric = {
id: faker.string.uuid(),
value: config.value,
unit: config.unit,
timestamp: Date.now(),
formattedTime: $$AnalyticsUtils.formatTimestamp(Date.now()),
hash: $$AnalyticsUtils.generateDataHash({ value: config.value }),
};
// Use global utility for validation
if (config.slaThreshold) {
metric.withinSLA = $$AnalyticsUtils.isWithinSLA(
config.value,
config.slaThreshold
);
}
return metric;
}Best Practices
1. Always Validate Complex Parameters
// ✅ Good: Validated parameters with defaults
import { z } from "zod";
const Schema = z.object({
count: z.number().int().positive().default(10),
type: z.enum(["user", "admin"]).default("user"),
});
async function generateUsers(options) {
const config = Schema.parse(options || {});
// ... use validated config
}
// ❌ Bad: No validation, runtime errors likely
async function generateUsers(options) {
const count = options.count; // Could be undefined, string, negative, etc.
// ... likely to fail
}2. Use Object Parameters for Flexibility
// ✅ Good: Flexible, extensible
async function createUser(options) {
const config = UserSchema.parse(options || {});
// Can add more options later without breaking existing calls
}
// ❌ Bad: Hard to extend
async function createUser(name, email, age, role, verified, country) {
// Adding more parameters breaks all existing calls
}3. Provide Sensible Defaults
import { z } from "zod";
const Schema = z.object({
count: z.number().int().positive().default(10), // Default to 10
includeMetadata: z.boolean().default(true), // Default to true
format: z.enum(["json", "xml"]).default("json"), // Default to json
});4. Use Descriptive Function Names
// ✅ Good: Clear intent
async function generateTestUserWithOrders() {
/* ... */
}
async function fetchValidatedPaymentData() {
/* ... */
}
async function calculateTotalWithTax() {
/* ... */
}
// ❌ Bad: Ambiguous
async function generate() {
/* ... */
}
async function getData() {
/* ... */
}
async function calc() {
/* ... */
}5. Test Before Activating
ReAPI provides built-in testing for generators:
- Write your function
- Use the UI testing feature to test with different parameters
- Verify the output is correct
- Only then activate for QA use
Important: Once activated and used in tests, avoid disabling—update instead or create versioned functions.
Key Takeaways
- Function Name Must Match UI Title - Not fixed like hooks
- Single Parameter Only - Use object for complex parameters
- Validate with Zod - Use
parse()for automatic validation and defaults - Async or Sync - Both supported, use async for HTTP/external calls
- Leverage Global Utilities - Call
$$Utilsfor shared logic - Provide Defaults - Make parameters optional with sensible defaults
- Test Thoroughly - Use built-in testing before activation
- UI-Accessible - QA can use via no-code UI or JS expressions
For comprehensive examples, see Build a Testing Framework.