Portal Community

Creating a Mock Context

// test/helpers/mockContext.ts
import type { FormActionContext } from '@atlas-forms/types-js';
import { vi } from 'vitest';

export function createMockContext(
  overrides?: Partial<FormActionContext>
): FormActionContext {
  return {
    formId: 'test-form-123',
    tenantId: 'tenant-42',
    userId: 'user-99',
    formValues: {},
    formEngine: {
      setFieldValue: vi.fn(),
      getValues: vi.fn().mockReturnValue({}),
      getFieldError: vi.fn().mockReturnValue(null),
    } as any,
    apiClient: {
      getFormById: vi.fn(),
      listForms: vi.fn(),
    } as any,
    navigate: vi.fn(),
    complete: vi.fn(),
    fail: vi.fn(),
    validate: vi.fn().mockResolvedValue(true),
    config: {},
    ...overrides,
  };
}

Unit Test Examples

// src/handlers/approveWorkflow.test.ts
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { approveWorkflowHandler } from './approveWorkflow';
import { createMockContext } from '../../test/helpers/mockContext';
import { approvalService } from '../services/approvalService';

vi.mock('../services/approvalService');

describe('approveWorkflowHandler', () => {
  let ctx: ReturnType<typeof createMockContext>;

  beforeEach(() => {
    ctx = createMockContext({
      formValues: {
        'workflow-instance-id': 'wf-001',
        'approver-notes': 'Looks good'
      },
      config: { transitionTo: 'approved' }
    });
  });

  it('calls approvalService.approve with correct args', async () => {
    vi.mocked(approvalService.approve).mockResolvedValue(undefined);
    await approveWorkflowHandler(ctx);

    expect(approvalService.approve).toHaveBeenCalledWith({
      workflowInstanceId: 'wf-001',
      tenantId: 'tenant-42',
      notes: 'Looks good',
      status: 'approved'
    });
  });

  it('calls ctx.complete() on success', async () => {
    vi.mocked(approvalService.approve).mockResolvedValue(undefined);
    await approveWorkflowHandler(ctx);
    expect(ctx.complete).toHaveBeenCalled();
  });

  it('calls ctx.fail() when service throws', async () => {
    vi.mocked(approvalService.approve).mockRejectedValue(new Error('Network error'));
    await approveWorkflowHandler(ctx);
    expect(ctx.fail).toHaveBeenCalledWith(expect.stringContaining('Network error'));
  });

  it('calls ctx.fail() when workflow-instance-id is missing', async () => {
    ctx.formValues = {};
    await approveWorkflowHandler(ctx);
    expect(ctx.fail).toHaveBeenCalled();
    expect(approvalService.approve).not.toHaveBeenCalled();
  });
});

Test Coverage Requirements

Aim for these coverage targets before publishing:

Test TypeRequiredCovers
Happy pathYesSuccessful execution, complete() called
Missing required valuesYesGuards that call fail() early
Service errorYescatch block calls fail(reason)
Validation failureIf usedvalidate() returns false path
Config variationIf configurableDifferent handlerConfig branches