Sim Testing Patterns and Utilities: A Complete Developer Guide for SimStudioAI

The Sim repository employs a Vitest-first testing strategy with a centralized @sim/testing package that provides mocks, factories, builders, and semantic assertions to ensure fast, deterministic, and maintainable test suites.

The SimStudioAI codebase maintains strict testing standards that keep workflow automation logic reliable and refactorable. Understanding the available testing patterns and utilities is essential for contributing to the monorepo, as every production module must co-locate a corresponding *.test.ts(x) file alongside its source. The architecture centers on the @sim/testing package, which eliminates duplicated test data and enforces consistent mocking strategies across API routes, UI components, and workflow executors.

The Vitest-First Architecture

Sim follows a co-location pattern where test files live beside their implementation targets. Vitest runs all tests with a global setup file that establishes the default environment and mocks heavy dependencies before any test code executes.

The foundation resides in packages/testing/src/setup/vitest.setup.ts, which registers global mocks for the database, authentication layer, and logging systems. This configuration enforces @vitest-environment node by default, ensuring server-side tests run optimally unless explicitly overridden for DOM-dependent component tests.

Every test file begins by importing the module under test after declaring its mocks. This static import order is critical because Vitest hoists vi.mock() calls to the top of the file scope. The setup file automatically clears mocks between runs, but individual test suites should include beforeEach(() => vi.clearAllMocks()) to ensure deterministic state.

Centralized Mocks and Dependency Isolation

Heavy dependencies live in packages/testing/src/mocks/*.mock.ts as pre-configured replacements. Each mock exports two objects: a default mock implementation and a *MockFns object containing the underlying vi.fn() instances for per-test customization.

Key centralized mocks include:

  • authMock – Replaces @/lib/auth with session and JWT utilities
  • dbMock – Stubs all @sim/db query operations (select, insert, update)
  • loggerMock – Captures logger.info and logger.error calls for verification
  • redisConfigMock – Mocks the Redis client configuration used by server-side caching

To use these mocks, declare them before importing the module under test:

/** @vitest-environment node */
import { authMock, authMockFns } from '@sim/testing'
import { beforeEach, describe, expect, it, vi } from 'vitest'

// Mock registration must precede the import under test
vi.mock('@/lib/auth', () => authMock)

import { GET } from '@/app/api/workflows/get/route'

Factories and Builders for Realistic Test Data

The @sim/testing package provides factories in packages/testing/src/factories/*.ts for generating domain objects with sensible defaults, and builders in packages/testing/src/builders/*.ts for assembling complex scenarios through a fluent API.

Factory functions create complete, valid objects instantly:

  • createLinearWorkflow(blockCount) – Generates a sequential workflow with N blocks
  • createBlock(type) – Creates a typed block (starter, action, condition) with unique IDs
  • createMockRequest(method, options) – Constructs a NextRequest object with proper headers, params, and body for API route testing

Builder classes support step-by-step construction of intricate structures:

  • WorkflowBuilder.branching() – Configures conditional paths and parallel execution edges
  • ExecutionContextBuilder – Assembles fully-typed execution contexts including variable states and block outputs
import { createLinearWorkflow, createMockRequest } from '@sim/testing'

// Generate a three-block workflow with automatic ID generation
const workflow = createLinearWorkflow(3)

// Build a GET request with URL parameters
const req = createMockRequest('GET', {
  params: { workflowId: workflow.id },
  query: { debug: 'true' }
})

Semantic Assertions and Domain-Specific Helpers

Low-level Jest-compatible assertions often obscure test intent. The Sim repository provides semantic assertion helpers in packages/testing/src/assertions/*.ts that encode domain knowledge about workflows, blocks, and execution graphs.

Available semantic assertions:

  • expectBlockExists(blocks, id, type) – Verifies a block exists in a collection with the expected type
  • expectBlockExecuted(executionLog, blockId) – Confirms a specific block ran during workflow execution
  • expectEdgeConnects(sourceId, targetId) – Validates that two blocks maintain a connection edge

These helpers hide implementation details like array indexing or-deep property access, making tests self-documenting:

import { expectBlockExists } from '@sim/testing'

const result = await response.json()
// Asserts first block exists and is a starter type
expectBlockExists(result.blocks, 'block-0', 'starter')

Testing Rules and Performance Guidelines

The .cursor/rules/sim-testing.mdc file codifies mandatory testing patterns that optimize for speed and reliability. Following these rules prevents common pitfalls that introduce flakiness or slow execution.

Critical testing rules:

  1. Never use vi.resetModules() – This clears the module cache and breaks mock registrations; prefer vi.clearAllMocks() instead
  2. Always mock heavy transitive dependencies – Database connections, external APIs, and authentication layers must be stubbed before import
  3. Use absolute imports – Import from @sim/testing rather than relative paths to ensure mock factories resolve correctly
  4. Prefer static vi.mock() over dynamic mocks – Static hoisting ensures mocks are established before module evaluation

Complete Implementation Example

The following example demonstrates a full unit test for a workflow API route, combining mock registration, factory usage, and semantic assertions:

/** @vitest-environment node */
import { createMockRequest, authMock, authMockFns, dbMockFns, expectBlockExists } from '@sim/testing'
import { beforeEach, describe, expect, it, vi } from 'vitest'

// Centralised mock for the auth module - must precede route import
vi.mock('@/lib/auth', () => authMock)

// Import the route handler AFTER the mock is registered
import { GET } from '@/app/api/workflows/get/route'

describe('GET /api/workflows/:workflowId', () => {
  beforeEach(() => {
    vi.clearAllMocks()
    // Configure the mocked session for this test case
    authMockFns.mockGetSession.mockResolvedValue({ user: { id: 'user-1' } })
  })

  it('returns the workflow data', async () => {
    // Build a simple linear workflow with three blocks
    const workflow = createLinearWorkflow(3)

    // Stub the database query that the route uses internally
    dbMockFns.mockSelect.mockReturnValue({
      from: () => ({
        where: () => Promise.resolve([workflow]),
      }),
    })

    const req = createMockRequest('GET', {
      params: { workflowId: workflow.id },
    })
    const res = await GET(req)

    expect(res.status).toBe(200)
    const json = await res.json()
    expect(json).toMatchObject({ id: workflow.id })
    
    // Semantic assertion verifying block structure
    expectBlockExists(json.blocks, 'block-0', 'starter')
  })
})

Summary

  • Vitest serves as the sole test runner, with *.test.ts(x) files co-located beside source code in both apps/sim and packages/* directories.
  • The @sim/testing package exports centralized mocks (authMock, dbMock), factories (createLinearWorkflow), builders (WorkflowBuilder), and semantic assertions (expectBlockExists) from packages/testing/src/index.ts.
  • All tests must follow the mock-first pattern: declare vi.mock() with centralized mocks before importing the module under test.
  • Performance-critical rules in .cursor/rules/sim-testing.mdc prohibit vi.resetModules() and mandate absolute imports for cross-package dependencies.
  • Use beforeEach(() => vi.clearAllMocks()) to ensure test isolation while retaining module state for optimal execution speed.

Frequently Asked Questions

How do I mock the authentication layer in Sim tests?

Import authMock and authMockFns from @sim/testing, then call vi.mock('@/lib/auth', () => authMock) before importing your route handler or component. Configure specific user sessions by mutating authMockFns.mockGetSession.mockResolvedValue() within your beforeEach hook. This pattern is defined in packages/testing/src/mocks/auth.mock.ts.

What is the difference between factories and builders in @sim/testing?

Factories (e.g., createLinearWorkflow, createMockRequest) are pure functions that return complete, ready-to-use objects with sensible defaults. Builders (e.g., WorkflowBuilder, ExecutionContextBuilder) are stateful classes providing fluent methods like .branching() or .withVariable() for step-by-step construction of complex scenarios. Use factories for simple cases and builders when you need conditional or multi-step test data assembly.

Why does Sim avoid vi.resetModules() in tests?

The repository prohibits vi.resetModules() because it clears Vitest's module cache, which destroys mock registrations established by vi.mock() at the file level. This causes subsequent tests to import unmocked dependencies, breaking isolation and potentially connecting to real databases or external services. Instead, use vi.clearAllMocks() to reset spy states while preserving the mock module graph.

Where should I place new test files in the Sim repository?

Place test files in the same directory as the source file they validate, using the naming convention [filename].test.ts for utilities and [filename].test.tsx for React components. For example, a component at apps/sim/components/workflow/card.tsx should have its tests at apps/sim/components/workflow/card.test.tsx. This co-location pattern ensures tests are discoverable and encourages maintenance when modifying source code.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →