or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

bindings.mdcontainer.mddependency-injection.mdindex.mdmodules.mdparsing-utilities.mdresolver.mdtesting.md
tile.json

testing.mddocs/

Testing Support

Swap and mock functionality for replacing dependencies during testing, with easy restoration capabilities. The testing support allows you to replace real implementations with test doubles for isolated unit testing.

Capabilities

Swap Functionality

Define fake implementations for class constructors during testing.

/**
 * Defines a fake implementation for a class constructor
 * @param binding - The class constructor to swap
 * @param resolver - Factory function for the fake implementation
 */
swap<Binding extends AbstractConstructor<any>>(
  binding: Binding,
  resolver: BindingResolver<KnownBindings, InstanceType<Binding>>
): void;

Usage Examples:

import { Container, inject } from "@adonisjs/fold";

// Production classes
class EmailService {
  async send(to: string, message: string) {
    // Real email sending logic
    console.log(`Sending email to ${to}: ${message}`);
  }
}

class Logger {
  info(message: string) {
    console.log(`[INFO] ${message}`);
  }
}

@inject()
class UserService {
  constructor(
    private emailService: EmailService,
    private logger: Logger
  ) {}
  
  async createUser(userData: any) {
    this.logger.info("Creating user");
    const user = { id: 1, ...userData };
    await this.emailService.send(user.email, "Welcome!");
    return user;
  }
}

const container = new Container();

// Test setup with swaps
describe("UserService", () => {
  beforeEach(() => {
    // Swap EmailService with a fake
    container.swap(EmailService, () => ({
      send: jest.fn().mockResolvedValue(undefined)
    }));
    
    // Swap Logger with a test double
    container.swap(Logger, () => ({
      info: jest.fn()
    }));
  });
  
  afterEach(() => {
    // Restore original implementations
    container.restoreAll();
  });
  
  test("creates user with mocked dependencies", async () => {
    const userService = await container.make(UserService);
    const result = await userService.createUser({ email: "test@example.com" });
    
    expect(result).toEqual({ id: 1, email: "test@example.com" });
  });
});

Individual Restoration

Restore specific bindings by removing their swaps.

/**
 * Restores a binding by removing its swap
 * @param binding - The class constructor to restore
 */
restore(binding: AbstractConstructor<any>): void;

Usage Examples:

import { Container } from "@adonisjs/fold";

const container = new Container();

// Mock implementations
class MockEmailService {
  async send() { return "mocked"; }
}

class MockLogger {
  info() { console.log("mocked log"); }
}

// Set up swaps
container.swap(EmailService, () => new MockEmailService());
container.swap(Logger, () => new MockLogger());

// Test with both mocks
let userService = await container.make(UserService);
// UserService uses both MockEmailService and MockLogger

// Restore only EmailService
container.restore(EmailService);

// Test with partial restoration
userService = await container.make(UserService);
// UserService uses real EmailService but MockLogger

// Restore Logger as well
container.restore(Logger);

userService = await container.make(UserService);
// UserService uses both real implementations

Bulk Restoration

Restore multiple or all bindings by removing their swaps.

/**
 * Restores multiple or all bindings by removing their swaps
 * @param bindings - Array of class constructors to restore, or undefined to restore all
 */
restoreAll(bindings?: AbstractConstructor<any>[]): void;

Usage Examples:

import { Container } from "@adonisjs/fold";

const container = new Container();

// Set up multiple swaps
container.swap(EmailService, () => new MockEmailService());
container.swap(Logger, () => new MockLogger());
container.swap(Database, () => new MockDatabase());
container.swap(Cache, () => new MockCache());

// Restore specific bindings
container.restoreAll([EmailService, Logger]);
// Database and Cache swaps remain

// Restore all remaining swaps
container.restoreAll();
// All original implementations restored

// Common testing pattern
describe("Integration Tests", () => {
  beforeEach(() => {
    // Set up all mocks
    container.swap(EmailService, () => new MockEmailService());
    container.swap(Logger, () => new MockLogger());
    container.swap(Database, () => new InMemoryDatabase());
  });
  
  afterEach(() => {
    // Clean up all mocks
    container.restoreAll();
  });
  
  test("test case", async () => {
    // Test logic with mocked dependencies
  });
});

Swap Priority

Understanding how swaps interact with the binding resolution system.

Resolution Priority:

  1. Swaps (highest priority - only for class constructors)
  2. Aliases
  3. Binding values
  4. Registered bindings (bind/singleton)
  5. Class constructor resolution

Usage Examples:

import { Container } from "@adonisjs/fold";

const container = new Container();

// Various binding types for the same class
container.bindValue(Logger, new Logger("value_logger"));
container.bind(Logger, () => new Logger("bind_logger"));
container.singleton(Logger, () => new Logger("singleton_logger"));

// Without swap
let logger = await container.make(Logger);
console.log(logger); // Logger("value_logger") - bindValue wins

// With swap - overrides everything
container.swap(Logger, () => new Logger("swap_logger"));

logger = await container.make(Logger);
console.log(logger); // Logger("swap_logger") - swap wins

// Restore swap
container.restore(Logger);

logger = await container.make(Logger);
console.log(logger); // Logger("value_logger") - back to bindValue

Test Utilities

Common patterns for testing with dependency injection.

Usage Examples:

import { Container, inject } from "@adonisjs/fold";

// Test helper class
class TestContainer {
  private container: Container;
  private swaps: AbstractConstructor<any>[] = [];
  
  constructor() {
    this.container = new Container();
  }
  
  mock<T>(binding: AbstractConstructor<T>, implementation: Partial<T>): void {
    this.container.swap(binding, () => implementation as T);
    this.swaps.push(binding);
  }
  
  spy<T>(binding: AbstractConstructor<T>): T {
    const spy = jest.createMockFromModule(binding.name) as T;
    this.mock(binding, spy);
    return spy;
  }
  
  make<T>(binding: any): Promise<T> {
    return this.container.make(binding);
  }
  
  cleanup(): void {
    this.container.restoreAll(this.swaps);
    this.swaps = [];
  }
}

// Usage in tests
describe("UserService", () => {
  let testContainer: TestContainer;
  let emailSpy: EmailService;
  let loggerSpy: Logger;
  
  beforeEach(() => {
    testContainer = new TestContainer();
    
    // Create spies
    emailSpy = testContainer.spy(EmailService);
    loggerSpy = testContainer.spy(Logger);
    
    // Mock specific methods
    testContainer.mock(Database, {
      query: jest.fn().mockResolvedValue([]),
      insert: jest.fn().mockResolvedValue({ id: 1 })
    });
  });
  
  afterEach(() => {
    testContainer.cleanup();
  });
  
  test("user creation calls email service", async () => {
    const userService = await testContainer.make(UserService);
    await userService.createUser({ email: "test@example.com" });
    
    expect(emailSpy.send).toHaveBeenCalledWith(
      "test@example.com",
      "Welcome!"
    );
    expect(loggerSpy.info).toHaveBeenCalledWith("Creating user");
  });
});

Advanced Testing Patterns

Complex testing scenarios with conditional mocks and partial implementations.

Usage Examples:

import { Container, inject } from "@adonisjs/fold";

// Conditional mocking based on test environment
class TestHelper {
  static setupMocks(container: Container, options: {
    mockEmail?: boolean;
    mockDatabase?: boolean;
    mockCache?: boolean;
  }) {
    if (options.mockEmail) {
      container.swap(EmailService, () => ({
        send: jest.fn().mockResolvedValue(undefined)
      }));
    }
    
    if (options.mockDatabase) {
      container.swap(Database, () => new InMemoryDatabase());
    }
    
    if (options.mockCache) {
      container.swap(Cache, () => new Map());
    }
  }
}

describe("User Registration Flow", () => {
  let container: Container;
  
  beforeEach(() => {
    container = new Container();
  });
  
  afterEach(() => {
    container.restoreAll();
  });
  
  describe("Unit Tests", () => {
    beforeEach(() => {
      // Mock all external dependencies
      TestHelper.setupMocks(container, {
        mockEmail: true,
        mockDatabase: true,
        mockCache: true
      });
    });
    
    test("isolated user creation logic", async () => {
      // Test with all dependencies mocked
    });
  });
  
  describe("Integration Tests", () => {
    beforeEach(() => {
      // Mock only external services, use real database
      TestHelper.setupMocks(container, {
        mockEmail: true,
        mockCache: false
      });
    });
    
    test("user creation with real database", async () => {
      // Test with partial mocking
    });
  });
});