Skip to content

Testing

awilixify tests usually use the same module graph as the application and then disable or replace only the parts that should not run in a test.

Testing Through Mediator

awilixify encourages using the mediator for focused module tests when possible. It keeps tests close to the application module graph while avoiding transport-specific test setup.

You can bootstrap a module, resolve the mediator from the module scope, and execute queries or commands with full typings:

typescript
const app = DIContext.create(OwnersModule, {
  moduleOverrides: [
    overrideModule(OwnersModule, {
      providers: {
        ownersRepository: inMemoryOwnersRepository,
      },
    }),
  ],
});

const mediator = app.scope.resolve<Deps["queryMediator"]>("queryMediator");
// with complete type safety
const result = await mediator.execute("cats/get-cats", {});

For E2E-style tests, such as tests built with Supertest, you can still resolve the framework instance from the same module scope and test through HTTP

Module Overrides

Use moduleOverrides to override providers or other keyed features in any module in the bootstrapped graph, including the root module, global modules, imported modules, and nested imported modules.

typescript
const app = DIContext.create(OwnersModule, {
  moduleOverrides: [
    overrideModule(OwnersModule, {
      providers: {
        ownersService: FakeOwnersService,
        owners1Service: {
          useClass: FakeOwners1Service,
          lifetime: "SINGLETON",
        },
      },
    }),
  ],
});

Provider overrides use the same provider syntax as module.providers:

typescript
overrideModule(OwnersModule, {
  providers: {
    service: FakeService,
    repository: { useClass: InMemoryRepository },
    config: testConfig,
    db: {
      useFactory: () => createTestDb(),
    },
  },
});

IMPORTANT

An override is a complete feature definition replacement. Original provider options such as lifetime, eager, initAfter, and allowCircular are not preserved unless repeated in the override.

Provider override typing checks the public methods of the original provider. Private and protected members are ignored so separately declared test class can be used without extending the original class.

overrideModule matches modules by object identity. For dynamic modules, hoist the module instance if tests need to override it:

typescript
export const CatsCacheModule = CacheModule("cats");

export const CatsModule = createModule<CatsModuleDef>({
  name: "CatsModule",
  imports: [CatsCacheModule],
});

const app = DIContext.create(AppModule, {
  moduleOverrides: [
    overrideModule(CatsCacheModule, {
      providers: {
        cache: testCache,
      },
    }),
  ],
});

You can override other keyed module features as well:

typescript
DIContext.create(AppModule, {
  moduleOverrides: [
    overrideModule(CatsModule, {
      providers: {
        catsService: FakeCatsService,
      },
      queryPreHandlers: {
        auth: AllowAllAuthMiddleware,
      },
      interceptors: {
        cache: NoopCacheInterceptor,
      },
      initializers: {
        cron: NoopCronInitializer,
      },
    }),
  ],
});

Only features declared by the target module can be overridden. If the target module is not found in the bootstrapped module graph, awilixify throws during bootstrap.

Skipping Bootstrap Work

awilixify has a few controls for disabling startup behavior while keeping the module graph available for tests.

Initializers

Initializers are startup wiring for decorated controller methods. In focused unit or module tests, you may want to bootstrap the container without registering routes, queue listeners, cron jobs, or message handlers.

typescript
const app = DIContext.create(OwnersModule, {
  globalModules: [ConfigModule],
});

await app.init({
  excludeInitializers: true,
});

You can also skip only selected initializer keys:

typescript
await app.init({
  excludeInitializers: ["cron", "onQueueJob"],
});

postInit

Use excludePostInit when eager provider init() should run, decorators should register routes/listeners, but the final startup side effect should not run.

This is useful for HTTP tests where the Fastify or Express app should be initialized but should not listen on a port.

typescript
const app = DIContext.create(AppModule, {
  globalModules: [ConfigModule, HttpModule],
});

await app.init({
  excludePostInit: ["fastifyService"],
});

You can skip all postInit() hooks:

typescript
await app.init({
  excludePostInit: true,
});

Manual Route Registration

skipRegisterRoutes disables bootstrap-time controller.registerRoutes() calls.

typescript
const app = DIContext.create(OwnersModule, {
  skipRegisterRoutes: true,
});

This does not disable controller registration. Decorator-based initializers can still use controller metadata during app.init() unless you also exclude initializers.