---
title: "Testing"
description: "Testing strategies and patterns for applications using the TraceRTM JavaScript SDK"
---

# Testing

Comprehensive guide to testing applications that use the TraceRTM JavaScript SDK.

## Overview

Testing SDK integrations requires:

1. **Unit tests** - Test your code in isolation with mocked SDK
2. **Integration tests** - Test against real or mock API
3. **End-to-end tests** - Test complete workflows

---

## Test Setup

### Install Test Dependencies

```bash
npm install --save-dev jest @types/jest ts-jest

# Or with Vitest
npm install --save-dev vitest @vitest/coverage-v8
```

### Jest Configuration

```javascript
// jest.config.js
module.exports = {
  preset: "ts-jest",
  testEnvironment: "node",
  roots: ["<rootDir>/src", "<rootDir>/tests"],
  testMatch: ["**/*.test.ts", "**/*.spec.ts"],
  collectCoverageFrom: ["src/**/*.ts", "!src/**/*.d.ts"],
  setupFilesAfterEnv: ["<rootDir>/tests/setup.ts"],
};
```

### Vitest Configuration

```typescript
// vitest.config.ts
import { defineConfig } from "vitest/config";

export default defineConfig({
  test: {
    globals: true,
    environment: "node",
    setupFiles: ["./tests/setup.ts"],
    coverage: {
      reporter: ["text", "json", "html"],
    },
  },
});
```

### Test Setup File

```typescript
// tests/setup.ts
import { TraceRTMClient } from "@tracertm/sdk";

// Set test API key
process.env.TRACERTM_API_KEY = "rtm_test_sk_test123";

// Global test timeout
jest.setTimeout(10000);

// Clear mocks between tests
beforeEach(() => {
  jest.clearAllMocks();
});
```

---

## Mocking the SDK

### Manual Mocking

```typescript
// tests/__mocks__/@tracertm/sdk.ts
export class TraceRTMClient {
  projects = {
    get: jest.fn(),
    list: jest.fn(),
    create: jest.fn(),
    update: jest.fn(),
    delete: jest.fn(),
  };

  items = {
    get: jest.fn(),
    list: jest.fn(),
    create: jest.fn(),
    update: jest.fn(),
    delete: jest.fn(),
  };

  links = {
    get: jest.fn(),
    list: jest.fn(),
    create: jest.fn(),
    delete: jest.fn(),
  };

  reports = {
    coverage: jest.fn(),
    traceabilityMatrix: jest.fn(),
    impactAnalysis: jest.fn(),
  };

  search = {
    items: jest.fn(),
  };
}

export const NotFoundError = class extends Error {
  name = "NotFoundError";
};

export const ValidationError = class extends Error {
  name = "ValidationError";
};
```

### Using the Mock

```typescript
// tests/services/project-service.test.ts
import { TraceRTMClient } from "@tracertm/sdk";
import { ProjectService } from "../../src/services/project-service";

jest.mock("@tracertm/sdk");

describe("ProjectService", () => {
  let client: jest.Mocked<TraceRTMClient>;
  let service: ProjectService;

  beforeEach(() => {
    client = new TraceRTMClient({ apiKey: "test" }) as jest.Mocked<TraceRTMClient>;
    service = new ProjectService(client);
  });

  test("getProject returns project", async () => {
    const mockProject = {
      id: "proj_123",
      name: "Test Project",
      key: "TP",
    };

    client.projects.get.mockResolvedValue(mockProject);

    const result = await service.getProject("proj_123");

    expect(client.projects.get).toHaveBeenCalledWith("proj_123");
    expect(result).toEqual(mockProject);
  });
});
```

---

## Test Factories

### Creating Test Data

```typescript
// tests/factories/index.ts
import type { Project, Item, Link } from "@tracertm/sdk";

let counter = 0;
const uniqueId = () => `test_${++counter}`;

export function createProject(overrides: Partial<Project> = {}): Project {
  return {
    id: uniqueId(),
    name: "Test Project",
    key: "TP",
    description: "A test project",
    ownerId: "user_123",
    settings: {},
    createdAt: new Date().toISOString(),
    updatedAt: new Date().toISOString(),
    ...overrides,
  };
}

export function createItem(overrides: Partial<Item> = {}): Item {
  return {
    id: uniqueId(),
    projectId: "proj_123",
    type: "requirement",
    title: "Test Item",
    description: "A test item",
    status: "draft",
    statusCategory: "initial",
    priority: "medium",
    tags: [],
    createdBy: "user_123",
    fields: {},
    createdAt: new Date().toISOString(),
    updatedAt: new Date().toISOString(),
    ...overrides,
  };
}

export function createLink(overrides: Partial<Link> = {}): Link {
  return {
    id: uniqueId(),
    sourceId: "item_1",
    targetId: "item_2",
    type: "verified_by",
    createdBy: "user_123",
    createdAt: new Date().toISOString(),
    ...overrides,
  };
}

export function createPaginatedResponse<T>(data: T[], total?: number) {
  return {
    data,
    total: total ?? data.length,
    limit: 100,
    offset: 0,
    hasMore: false,
  };
}
```

### Using Factories

```typescript
import { createProject, createItem, createPaginatedResponse } from "../factories";

test("lists items in project", async () => {
  const project = createProject({ id: "proj_test" });
  const items = [
    createItem({ projectId: project.id, title: "Item 1" }),
    createItem({ projectId: project.id, title: "Item 2" }),
  ];

  client.items.list.mockResolvedValue(createPaginatedResponse(items));

  const result = await service.listItems(project.id);

  expect(result).toHaveLength(2);
  expect(result[0].title).toBe("Item 1");
});
```

---

## Testing Async Operations

### Testing Promises

```typescript
test("handles successful response", async () => {
  const mockProject = createProject();
  client.projects.get.mockResolvedValue(mockProject);

  const result = await service.getProject("proj_123");

  expect(result).toEqual(mockProject);
});

test("handles rejection", async () => {
  client.projects.get.mockRejectedValue(new NotFoundError("Not found"));

  await expect(service.getProject("invalid")).rejects.toThrow(NotFoundError);
});
```

### Testing with async/await

```typescript
test("creates project with items", async () => {
  const mockProject = createProject();
  const mockItem = createItem({ projectId: mockProject.id });

  client.projects.create.mockResolvedValue(mockProject);
  client.items.create.mockResolvedValue(mockItem);

  const result = await service.createProjectWithItems("New Project", [
    { title: "Req 1", type: "requirement" },
  ]);

  expect(client.projects.create).toHaveBeenCalledWith(
    expect.objectContaining({ name: "New Project" })
  );
  expect(client.items.create).toHaveBeenCalled();
  expect(result.project).toEqual(mockProject);
});
```

### Testing Concurrent Operations

```typescript
test("fetches multiple projects concurrently", async () => {
  const projects = [
    createProject({ id: "proj_1" }),
    createProject({ id: "proj_2" }),
    createProject({ id: "proj_3" }),
  ];

  client.projects.get
    .mockResolvedValueOnce(projects[0])
    .mockResolvedValueOnce(projects[1])
    .mockResolvedValueOnce(projects[2]);

  const result = await service.getMultipleProjects(["proj_1", "proj_2", "proj_3"]);

  expect(client.projects.get).toHaveBeenCalledTimes(3);
  expect(result).toHaveLength(3);
});
```

---

## Testing Error Handling

### Testing Error Types

```typescript
import { NotFoundError, ValidationError, RateLimitError } from "@tracertm/sdk/errors";

test("handles NotFoundError", async () => {
  client.items.get.mockRejectedValue(new NotFoundError("Item not found"));

  const result = await service.findItem("invalid_id");

  expect(result).toBeNull();
});

test("handles ValidationError", async () => {
  client.items.create.mockRejectedValue(
    new ValidationError("Invalid input", [
      { field: "title", message: "Required" },
    ])
  );

  await expect(
    service.createItem({ title: "" })
  ).rejects.toThrow(ValidationError);
});

test("handles RateLimitError with retry", async () => {
  const rateLimitError = new RateLimitError("Rate limited");
  rateLimitError.retryAfter = 1;

  client.items.list
    .mockRejectedValueOnce(rateLimitError)
    .mockResolvedValueOnce(createPaginatedResponse([]));

  const result = await service.listItemsWithRetry("proj_123");

  expect(client.items.list).toHaveBeenCalledTimes(2);
  expect(result).toEqual([]);
});
```

### Testing Error Recovery

```typescript
test("recovers from transient errors", async () => {
  const items = [createItem()];

  client.items.list
    .mockRejectedValueOnce(new Error("Network error"))
    .mockRejectedValueOnce(new Error("Timeout"))
    .mockResolvedValueOnce(createPaginatedResponse(items));

  const result = await service.listItemsWithRetry("proj_123", { maxRetries: 3 });

  expect(client.items.list).toHaveBeenCalledTimes(3);
  expect(result).toEqual(items);
});
```

---

## Integration Tests

### Test Against Mock Server

```typescript
// tests/integration/project.integration.test.ts
import { TraceRTMClient } from "@tracertm/sdk";
import { setupMockServer } from "../helpers/mock-server";

describe("Project Integration", () => {
  const server = setupMockServer();
  let client: TraceRTMClient;

  beforeAll(() => server.listen());
  afterEach(() => server.resetHandlers());
  afterAll(() => server.close());

  beforeEach(() => {
    client = new TraceRTMClient({
      apiKey: "test_key",
      baseUrl: "http://localhost:3000",
    });
  });

  test("creates and retrieves project", async () => {
    const created = await client.projects.create({
      name: "Integration Test",
      key: "INT",
    });

    expect(created.id).toBeDefined();

    const retrieved = await client.projects.get(created.id);

    expect(retrieved.name).toBe("Integration Test");
  });
});
```

### Using MSW (Mock Service Worker)

```typescript
// tests/helpers/mock-server.ts
import { setupServer } from "msw/node";
import { rest } from "msw";

const handlers = [
  rest.get("http://localhost:3000/v1/projects/:id", (req, res, ctx) => {
    return res(
      ctx.json({
        id: req.params.id,
        name: "Mock Project",
        key: "MP",
      })
    );
  }),

  rest.post("http://localhost:3000/v1/projects", async (req, res, ctx) => {
    const body = await req.json();
    return res(
      ctx.status(201),
      ctx.json({
        id: "proj_new",
        ...body,
      })
    );
  }),

  rest.get("http://localhost:3000/v1/items", (req, res, ctx) => {
    const projectId = req.url.searchParams.get("projectId");
    return res(
      ctx.json({
        data: [{ id: "item_1", projectId, title: "Item 1" }],
        total: 1,
        limit: 100,
        offset: 0,
        hasMore: false,
      })
    );
  }),
];

export const setupMockServer = () => setupServer(...handlers);
```

---

## Testing Best Practices

### 1. Isolate Tests

```typescript
describe("ItemService", () => {
  let client: jest.Mocked<TraceRTMClient>;
  let service: ItemService;

  // Fresh setup for each test
  beforeEach(() => {
    client = new TraceRTMClient({ apiKey: "test" }) as jest.Mocked<TraceRTMClient>;
    service = new ItemService(client);
  });

  // Tests are independent
  test("test 1", () => { /* ... */ });
  test("test 2", () => { /* ... */ });
});
```

### 2. Test Edge Cases

```typescript
describe("pagination", () => {
  test("handles empty results", async () => {
    client.items.list.mockResolvedValue(createPaginatedResponse([]));
    const result = await service.getAllItems("proj_123");
    expect(result).toEqual([]);
  });

  test("handles single page", async () => {
    const items = [createItem()];
    client.items.list.mockResolvedValue(createPaginatedResponse(items));
    const result = await service.getAllItems("proj_123");
    expect(result).toHaveLength(1);
  });

  test("handles multiple pages", async () => {
    const page1 = [createItem(), createItem()];
    const page2 = [createItem()];

    client.items.list
      .mockResolvedValueOnce({ ...createPaginatedResponse(page1), hasMore: true })
      .mockResolvedValueOnce(createPaginatedResponse(page2));

    const result = await service.getAllItems("proj_123");
    expect(result).toHaveLength(3);
  });
});
```

### 3. Use Descriptive Test Names

```typescript
describe("ItemService.createItem", () => {
  test("creates item with minimum required fields", async () => { /* ... */ });
  test("creates item with all optional fields", async () => { /* ... */ });
  test("throws ValidationError when title is empty", async () => { /* ... */ });
  test("throws ValidationError when type is invalid", async () => { /* ... */ });
  test("returns created item with generated ID", async () => { /* ... */ });
});
```

### 4. Test Public API, Not Implementation

```typescript
// Good - tests behavior
test("returns null when item not found", async () => {
  client.items.get.mockRejectedValue(new NotFoundError("Not found"));
  const result = await service.findItem("invalid");
  expect(result).toBeNull();
});

// Bad - tests implementation details
test("catches NotFoundError and returns null", async () => {
  // This is testing how, not what
});
```

---

## Related Documentation

- [Jest Tests](/docs/api-reference/sdks/javascript/jest-tests) - Jest-specific patterns
- [Mocking](/docs/api-reference/sdks/javascript/mocking) - Mocking strategies
- [Error Handling](/docs/api-reference/sdks/javascript/error-handling) - Error types
