Yasumu includes a powerful test automation system that allows you to write automated tests for your API endpoints using JavaScript/TypeScript. Tests execute in the same Deno-powered runtime as pre/post scripts, with full access to request and response data.
Writing tests
Tests are defined in the test block of REST entity .ysl files and execute after receiving a response:
export function onTest ( req , res ) {
// Test response status
expect ( res . status ). toBe ( 200 );
// Test response body
const data = res . json ();
expect ( data ). toHaveProperty ( 'id' );
expect ( data . email ). toBeDefined ();
expect ( data . name ). toBe ( 'John Doe' );
}
The onTest function receives the same req and res objects as post-response scripts, giving you full access to request and response data.
Test execution
Tests are executed automatically when you send a request:
public async executeTest (
entityId : string ,
script : YasumuEmbeddedScript ,
context : RestScriptContext ,
) {
const result =
await this . workspace . manager . yasumu . rpc . rest . executeScript . $mutate ({
parameters: [
{
entityId ,
script ,
context ,
invocationTarget: 'onTest' ,
},
],
});
return result ;
}
Test results include:
export interface TestResult {
test : string ; // Test name
result : 'pass' | 'fail' | 'skip' ; // Test outcome
error : string | null ; // Error message if failed
duration : number ; // Execution time in milliseconds
}
Assertion library
Yasumu uses a Jest/Vitest-compatible expect assertion library with comprehensive matchers:
Basic matchers
Equality
Truthiness
Numbers
Strings
expect ( res . status ). toBe ( 200 );
expect ( res . status ). not . toBe ( 404 );
expect ( data . id ). toEqual ( 123 );
expect ( data . tags ). toEqual ([ 'api' , 'rest' ]);
toBe(): Strict equality (===)
toEqual(): Deep equality for objects/arrays
expect ( data . active ). toBeTruthy ();
expect ( data . deleted ). toBeFalsy ();
expect ( data . id ). toBeDefined ();
expect ( data . avatar ). toBeUndefined ();
expect ( data . value ). toBeNull ();
expect ( data . result ). not . toBeNull ();
expect ( res . status ). toBeGreaterThan ( 199 );
expect ( res . status ). toBeLessThan ( 300 );
expect ( res . status ). toBeGreaterThanOrEqual ( 200 );
expect ( res . status ). toBeLessThanOrEqual ( 299 );
expect ( data . price ). toBeCloseTo ( 19.99 , 2 );
expect ( data . email ). toContain ( '@' );
expect ( data . url ). toMatch ( / ^ https ? : \/\/ / );
expect ( res . headers . get ( 'content-type' )). toContain ( 'application/json' );
Object and array matchers
Objects
Arrays
Type checking
// Check for properties
expect ( data ). toHaveProperty ( 'id' );
expect ( data ). toHaveProperty ( 'user.email' );
expect ( data ). toHaveProperty ( 'settings.theme' , 'dark' );
// Partial matching
expect ( data ). toMatchObject ({
name: 'John' ,
email: expect . any ( String ),
});
// Check keys
expect ( Object . keys ( data )). toContain ( 'id' );
// Length
expect ( data . items ). toHaveLength ( 10 );
// Contains
expect ( data . tags ). toContain ( 'api' );
expect ( data . users ). toContainEqual ({ id: 1 , name: 'John' });
// Array matching
expect ( data . items ). toEqual (
expect . arrayContaining ([{ id: 1 }, { id: 2 }])
);
expect ( data . id ). toEqual ( expect . any ( Number ));
expect ( data . name ). toEqual ( expect . any ( String ));
expect ( data . tags ). toEqual ( expect . any ( Array ));
expect ( data . metadata ). toEqual ( expect . any ( Object ));
// Instance checks
expect ( new Date ()). toBeInstanceOf ( Date );
Test organization
Organize tests using the describe and test functions:
export function onTest ( req , res ) {
describe ( 'User API' , () => {
test ( 'returns 200 status' , () => {
expect ( res . status ). toBe ( 200 );
});
test ( 'returns valid user data' , () => {
const data = res . json ();
expect ( data ). toHaveProperty ( 'id' );
expect ( data ). toHaveProperty ( 'email' );
expect ( data ). toHaveProperty ( 'name' );
});
test ( 'email format is valid' , () => {
const data = res . json ();
expect ( data . email ). toMatch ( / ^ [ ^ @ ] + @ [ ^ @ ] + \. [ ^ @ ] + $ / );
});
});
}
Use describe to group related tests and test to define individual test cases. This improves test organization and makes results easier to read.
Common test patterns
Status code validation
export function onTest ( req , res ) {
describe ( 'Response status' , () => {
test ( 'returns success status' , () => {
expect ( res . status ). toBeGreaterThanOrEqual ( 200 );
expect ( res . status ). toBeLessThan ( 300 );
});
test ( 'status text is OK' , () => {
expect ( res . statusText ). toBe ( 'OK' );
});
});
}
export function onTest ( req , res ) {
describe ( 'Response headers' , () => {
test ( 'has correct content type' , () => {
expect ( res . headers . get ( 'content-type' )). toContain ( 'application/json' );
});
test ( 'includes CORS headers' , () => {
expect ( res . headers . get ( 'access-control-allow-origin' )). toBeDefined ();
});
test ( 'has cache control' , () => {
expect ( res . headers . get ( 'cache-control' )). toBeTruthy ();
});
});
}
Response body structure
export function onTest ( req , res ) {
const data = res . json ();
describe ( 'User object structure' , () => {
test ( 'has required fields' , () => {
expect ( data ). toHaveProperty ( 'id' );
expect ( data ). toHaveProperty ( 'email' );
expect ( data ). toHaveProperty ( 'name' );
expect ( data ). toHaveProperty ( 'createdAt' );
});
test ( 'field types are correct' , () => {
expect ( data . id ). toEqual ( expect . any ( Number ));
expect ( data . email ). toEqual ( expect . any ( String ));
expect ( data . name ). toEqual ( expect . any ( String ));
expect ( data . active ). toEqual ( expect . any ( Boolean ));
});
test ( 'has nested objects' , () => {
expect ( data ). toHaveProperty ( 'profile.avatar' );
expect ( data ). toHaveProperty ( 'settings.theme' );
});
});
}
Data validation
export function onTest ( req , res ) {
const data = res . json ();
describe ( 'Data validation' , () => {
test ( 'email is valid format' , () => {
expect ( data . email ). toMatch ( / ^ [ ^ @ ] + @ [ ^ @ ] + \. [ ^ @ ] + $ / );
});
test ( 'ID is positive integer' , () => {
expect ( data . id ). toBeGreaterThan ( 0 );
expect ( Number . isInteger ( data . id )). toBe ( true );
});
test ( 'timestamps are valid' , () => {
const created = new Date ( data . createdAt );
expect ( created . getTime ()). toBeGreaterThan ( 0 );
expect ( created . getTime ()). toBeLessThanOrEqual ( Date . now ());
});
test ( 'enum values are valid' , () => {
expect ([ 'active' , 'inactive' , 'pending' ]). toContain ( data . status );
});
});
}
Array responses
export function onTest ( req , res ) {
const data = res . json ();
describe ( 'User list' , () => {
test ( 'returns array' , () => {
expect ( Array . isArray ( data . items )). toBe ( true );
});
test ( 'has pagination info' , () => {
expect ( data ). toHaveProperty ( 'total' );
expect ( data ). toHaveProperty ( 'page' );
expect ( data ). toHaveProperty ( 'pageSize' );
});
test ( 'items have correct structure' , () => {
data . items . forEach ( item => {
expect ( item ). toHaveProperty ( 'id' );
expect ( item ). toHaveProperty ( 'name' );
});
});
test ( 'respects page size' , () => {
const pageSize = parseInt ( req . env . getVariable ( 'pageSize' ) || '10' );
expect ( data . items . length ). toBeLessThanOrEqual ( pageSize );
});
});
}
Error responses
export function onTest ( req , res ) {
describe ( 'Error handling' , () => {
if ( res . status >= 400 ) {
test ( 'has error status' , () => {
expect ( res . status ). toBeGreaterThanOrEqual ( 400 );
});
test ( 'includes error message' , () => {
const error = res . json ();
expect ( error ). toHaveProperty ( 'message' );
expect ( error . message ). toBeTruthy ();
});
test ( 'includes error code' , () => {
const error = res . json ();
expect ( error ). toHaveProperty ( 'code' );
expect ( error . code ). toMatch ( / ^ [ A-Z_ ] + $ / );
});
} else {
test ( 'request succeeded' , () => {
expect ( res . status ). toBeLessThan ( 400 );
});
}
});
}
Environment-based testing
Tests can adapt based on the active environment:
export function onTest ( req , res ) {
const environment = req . env . getVariable ( 'environment' );
const data = res . json ();
describe ( 'Environment-specific tests' , () => {
if ( environment === 'production' ) {
test ( 'uses HTTPS' , () => {
expect ( req . url ). toMatch ( / ^ https:/ );
});
test ( 'has security headers' , () => {
expect ( res . headers . get ( 'strict-transport-security' )). toBeDefined ();
});
}
if ( environment === 'development' ) {
test ( 'includes debug info' , () => {
expect ( data ). toHaveProperty ( '_debug' );
});
}
});
}
Test result storage
Test results are cached in the entity metadata:
export interface RestEntityMetadata {
responseCache : {
status : number ;
statusText : string ;
headers : Record < string , string >;
body : string | null ;
};
requestCache : {
binaryPaths : { [ key : string ] : string | null };
};
testResultCache : TestResult [];
}
Access test results:
const entity = await workspace . rest . get ( requestId );
const testResults = entity . data . metadata ?. testResultCache || [];
// Analyze results
const passed = testResults . filter ( t => t . result === 'pass' ). length ;
const failed = testResults . filter ( t => t . result === 'fail' ). length ;
const totalTime = testResults . reduce (( sum , t ) => sum + t . duration , 0 );
console . log ( ` ${ passed } passed, ${ failed } failed in ${ totalTime } ms` );
Postman test migration
If you’re migrating from Postman, Yasumu automatically converts test syntax:
script-transformer.ts:152-210
// Postman syntax
pm . expect ( res . status ). to . eql ( 200 );
pm . expect ( res . json ()). to . have . property ( 'id' );
pm . test ( 'Status is 200' , () => {
pm . response . to . have . status ( 200 );
});
// Converted to Yasumu syntax
export function onTest ( req , res ) {
expect ( res . status ). toEqual ( 200 );
expect ( res . json ()). toHaveProperty ( 'id' );
test ( 'Status is 200' , () => {
expect ( res . status ). toBe ( 200 );
});
}
The import tool automatically converts Postman test syntax to Yasumu’s expect-style assertions.
Advanced testing techniques
Response time testing
export function onTest ( req , res ) {
const startTime = parseInt ( req . env . getVariable ( 'requestStartTime' ));
const duration = Date . now () - startTime ;
describe ( 'Performance' , () => {
test ( 'responds within acceptable time' , () => {
expect ( duration ). toBeLessThan ( 1000 ); // Less than 1 second
});
});
// Store for trending
req . env . setVariable ( 'lastResponseTime' , duration . toString ());
}
Schema validation
export function onTest ( req , res ) {
const data = res . json ();
const userSchema = {
id: 'number' ,
email: 'string' ,
name: 'string' ,
active: 'boolean' ,
profile: 'object' ,
tags: 'array' ,
};
describe ( 'Schema validation' , () => {
Object . entries ( userSchema ). forEach (([ key , type ]) => {
test ( ` ${ key } is ${ type } ` , () => {
expect ( data ). toHaveProperty ( key );
if ( type === 'array' ) {
expect ( Array . isArray ( data [ key ])). toBe ( true );
} else if ( type === 'object' ) {
expect ( typeof data [ key ]). toBe ( 'object' );
expect ( data [ key ]). not . toBeNull ();
} else {
expect ( typeof data [ key ]). toBe ( type );
}
});
});
});
}
Conditional tests
export function onTest ( req , res ) {
const data = res . json ();
describe ( 'User profile' , () => {
// Always run
test ( 'has basic info' , () => {
expect ( data ). toHaveProperty ( 'id' );
expect ( data ). toHaveProperty ( 'name' );
});
// Conditional test
if ( data . type === 'premium' ) {
test ( 'premium user has subscription' , () => {
expect ( data ). toHaveProperty ( 'subscription' );
expect ( data . subscription ). toHaveProperty ( 'plan' );
expect ( data . subscription ). toHaveProperty ( 'expiresAt' );
});
}
// Run test only if property exists
if ( data . avatar ) {
test ( 'avatar URL is valid' , () => {
expect ( data . avatar ). toMatch ( / ^ https ? : \/\/ . + / );
});
}
});
}
Best practices
Test one thing Each test should validate a single aspect of the response
Use descriptive names Test names should clearly describe what they validate
Group related tests Use describe blocks to organize tests by feature or component
Test edge cases Include tests for error conditions and boundary values
Keep tests independent Tests should not depend on the execution order
Use environment variables Make tests reusable across environments with variables
Run tests regularly during development to catch regressions early. Consider setting up automated test runs in CI/CD pipelines.