Skip to main content
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:
rest.ts:117-135
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:
rest.types.ts:23-40
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

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

Object and array matchers

// 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');

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');
    });
  });
}

Response headers

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:
rest.types.ts:42-56
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.

Build docs developers (and LLMs) love