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

> Explore SimStudioAI Sim testing patterns and utilities for fast, deterministic, and maintainable test suites. Discover mocks, factories, builders, and semantic assertions.

- Repository: [Sim/sim](https://github.com/simstudioai/sim)
- Tags: how-to-guide
- Published: 2026-05-02

---

**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`](https://github.com/simstudioai/sim/blob/main/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:

```typescript
/** @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

```typescript
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:

```typescript
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:

```typescript
/** @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`](https://github.com/simstudioai/sim/blob/main/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`](https://github.com/simstudioai/sim/blob/main/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`](https://github.com/simstudioai/sim/blob/main/apps/sim/components/workflow/card.tsx) should have its tests at [`apps/sim/components/workflow/card.test.tsx`](https://github.com/simstudioai/sim/blob/main/apps/sim/components/workflow/card.test.tsx). This co-location pattern ensures tests are discoverable and encourages maintenance when modifying source code.