CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-nestjs--testing

Comprehensive testing utilities for NestJS applications with dependency injection testing capabilities

Pending
Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Pending

The risk profile of this skill

Overview
Eval results
Files

module-overriding.mddocs/

Module Overriding

Capability to replace entire modules with alternative implementations for testing scenarios with complex module dependencies. This is particularly useful for testing applications that depend on external modules or when you need to replace entire feature modules with simplified test versions.

Capabilities

Module Override Method

The TestingModuleBuilder provides a method to override entire modules with alternative implementations.

class TestingModuleBuilder {
  /**
   * Override an entire module with a replacement module
   * @param moduleToOverride - The module definition to replace
   * @returns OverrideModule interface for specifying the replacement module
   */
  overrideModule(moduleToOverride: ModuleDefinition): OverrideModule;
}

OverrideModule Interface

Interface for specifying module replacement strategies.

/**
 * Interface for replacing modules in testing scenarios
 */
interface OverrideModule {
  /**
   * Replace the target module with a new module implementation
   * @param newModule - The module definition to use as replacement
   * @returns TestingModuleBuilder for method chaining
   */
  useModule(newModule: ModuleDefinition): TestingModuleBuilder;
}

Usage Examples:

import { Test, TestingModule } from "@nestjs/testing";
import { Module } from "@nestjs/common";
import { DatabaseModule } from "./database.module";
import { UsersModule } from "./users.module";
import { UsersService } from "./users.service";

// Create a test replacement for DatabaseModule
@Module({
  providers: [
    {
      provide: "DATABASE_CONNECTION",
      useValue: {
        query: jest.fn(),
        close: jest.fn(),
      },
    },
  ],
  exports: ["DATABASE_CONNECTION"],
})
class TestDatabaseModule {}

describe("Module Overriding", () => {
  let module: TestingModule;

  beforeEach(async () => {
    module = await Test.createTestingModule({
      imports: [UsersModule], // UsersModule depends on DatabaseModule
    })
      .overrideModule(DatabaseModule)
      .useModule(TestDatabaseModule)
      .compile();
  });

  it("should use test database module", async () => {
    const connection = module.get("DATABASE_CONNECTION");
    expect(connection.query).toBeDefined();
    expect(typeof connection.query).toBe("function");
  });
});

Advanced Module Override Patterns

Dynamic Module Overriding:

import { DynamicModule } from "@nestjs/common";

// Original dynamic module
@Module({})
class ConfigModule {
  static forRoot(options: ConfigOptions): DynamicModule {
    return {
      module: ConfigModule,
      providers: [
        {
          provide: "CONFIG_OPTIONS",
          useValue: options,
        },
        ConfigService,
      ],
      exports: [ConfigService],
    };
  }
}

// Test replacement
@Module({})
class TestConfigModule {
  static forRoot(): DynamicModule {
    return {
      module: TestConfigModule,
      providers: [
        {
          provide: "CONFIG_OPTIONS",
          useValue: { env: "test" },
        },
        {
          provide: ConfigService,
          useValue: {
            get: jest.fn().mockReturnValue("test-value"),
          },
        },
      ],
      exports: [ConfigService],
    };
  }
}

const module = await Test.createTestingModule({
  imports: [AppModule], // AppModule imports ConfigModule.forRoot()
})
  .overrideModule(ConfigModule)
  .useModule(TestConfigModule.forRoot())
  .compile();

Third-party Module Overriding:

// Override external modules like TypeORM, Mongoose, etc.
import { TypeOrmModule } from "@nestjs/typeorm";
import { getRepositoryToken } from "@nestjs/typeorm";
import { User } from "./user.entity";

@Module({
  providers: [
    {
      provide: getRepositoryToken(User),
      useValue: {
        find: jest.fn(),
        findOne: jest.fn(),
        save: jest.fn(),
        delete: jest.fn(),
      },
    },
  ],
  exports: [getRepositoryToken(User)],
})
class TestTypeOrmModule {}

const module = await Test.createTestingModule({
  imports: [UsersModule], // UsersModule imports TypeOrmModule.forFeature([User])
})
  .overrideModule(TypeOrmModule)
  .useModule(TestTypeOrmModule)
  .compile();

Feature Module Simplification:

// Original complex feature module
@Module({
  imports: [
    DatabaseModule,
    RedisModule,
    EventEmitterModule,
    HttpModule,
  ],
  providers: [
    ComplexService,
    BackgroundProcessor,
    ExternalApiClient,
  ],
  exports: [ComplexService],
})
class FeatureModule {}

// Simplified test version
@Module({
  providers: [
    {
      provide: ComplexService,
      useValue: {
        processData: jest.fn().mockResolvedValue("processed"),
        validateInput: jest.fn().mockReturnValue(true),
      },
    },
  ],
  exports: [ComplexService],
})
class TestFeatureModule {}

const module = await Test.createTestingModule({
  imports: [AppModule],
})
  .overrideModule(FeatureModule)
  .useModule(TestFeatureModule)
  .compile();

Module Override with Providers

// Override module and add additional test providers
@Module({
  providers: [
    // Mock the original module's providers
    {
      provide: OriginalService,
      useValue: mockOriginalService,
    },
    // Add test-specific utilities
    TestUtilityService,
  ],
  exports: [OriginalService, TestUtilityService],
})
class TestReplacementModule {}

const module = await Test.createTestingModule({
  imports: [MainModule],
})
  .overrideModule(OriginalModule)
  .useModule(TestReplacementModule)
  .compile();

// Access both original and test utilities
const originalService = module.get<OriginalService>(OriginalService);
const testUtility = module.get<TestUtilityService>(TestUtilityService);

Nested Module Overriding

// When modules have nested dependencies
const module = await Test.createTestingModule({
  imports: [AppModule],
})
  // Override multiple modules in the dependency chain
  .overrideModule(DatabaseModule)
  .useModule(TestDatabaseModule)
  .overrideModule(CacheModule)
  .useModule(TestCacheModule)
  .overrideModule(EventModule)
  .useModule(TestEventModule)
  .compile();

Error Handling

Common scenarios and error handling when overriding modules:

// Handle circular dependencies
try {
  const module = await Test.createTestingModule({
    imports: [ModuleWithCircularDep],
  })
    .overrideModule(CircularModule)
    .useModule(TestCircularModule)
    .compile();
} catch (error) {
  // Handle circular dependency errors
  console.error("Circular dependency detected:", error.message);
}

// Handle missing module dependencies
@Module({
  imports: [MissingDependency], // This might cause issues
  providers: [TestService],
})
class ProblematicTestModule {}

// Better approach - provide all necessary dependencies
@Module({
  providers: [
    TestService,
    {
      provide: "MISSING_DEPENDENCY",
      useValue: mockDependency,
    },
  ],
})
class WellFormedTestModule {}

Types

import { ModuleDefinition } from "@nestjs/core/interfaces/module-definition.interface";
import { TestingModuleBuilder } from "./testing-module.builder";

interface OverrideModule {
  useModule: (newModule: ModuleDefinition) => TestingModuleBuilder;
}

// ModuleDefinition is typically a class decorated with @Module()
type ModuleDefinition = Function;

docs

application-testing.md

index.md

mock-integration.md

module-building.md

module-overriding.md

provider-overriding.md

tile.json