TestingModuleBuilder
class in meocord/testing Since 4.0.0
class TestingModuleBuilderBuilds a testing module, with stand-ins for the providers, stages and theme a test replaces.
Use its override* methods before compile() to replace what a handler depends on, then run the handler with the
TestingModule it returns.
Examples
@Controller()
class ProfileController {
constructor(private readonly profiles: ProfileService) {}
@Command('profile', CommandType.SLASH)
@UseGuard(StaffGuard)
async profile(interaction: ChatInputCommandInteraction) {
await respond(interaction).send({ embeds: [await this.profiles.render(interaction.user.id)] })
}
}
const module = MeoCordTestingModule.create({ controllers: [ProfileController] })
.overrideProvider(ProfileService).useValue({ render: async () => new EmbedBuilder().setTitle('Ada') })
.overrideGuard(StaffGuard).useValue({ canActivate: () => true })
.compile()Members
constructor
new TestingModuleBuilder(
options: TestingModuleOptions,
wiring?: AppWiring | undefined,
)Parameters
| Name | Type | Since | Description |
|---|---|---|---|
options | TestingModuleOptions | ||
options.controllers? | (new (...args: any[]) => any)[] | The controllers the module builds, with every class they inject. | |
options.providers? | Provider[] | Providers for what those classes inject, in any shape | |
options.app? | new (...args: any[]) => unknown | 4.1.0 | The |
options.observers? | (new (...args: any[]) => DispatchObserver)[] | 4.1.0 |
|
options.shutdownTimeout? | number | 4.1.0 | How long |
wiring? | AppWiring | undefined | 4.1.0 |
compile
compile(): TestingModuleBinds the controllers, providers and overrides into a module ready to resolve and run handlers.
It runs the bot's startup checks on commands and autocomplete handlers, except a command no builder registers,
since a handler declared with a CommandType and no builder is how a test fixture is written. A subcommand path
its command's builder does not register, a customId pattern given as a command name, a builder that registers
another name, and component patterns that can match the same customId are named, as the bot names them.
Each check reports every startup error it finds, and the first is thrown, unchanged; when there are several, each
is logged first, with the file it comes from. After reportAllStartupErrors, the errors decorators kept on
the classes the module runs are among them.
Returns
TestingModuleThe compiled module.
Throws
Error for the first startup error the checks find.
overrideFilter Since 4.1.0
overrideFilter(filter: new (...args: any[]) => ExceptionFilter<any>): {
useValue: (stub: Partial<ExceptionFilter<any>>) => TestingModuleBuilder
}Replaces an exception filter with a stub wherever it applies. The filter's @Catch still decides
which errors reach the stub.
Parameters
| Name | Type | Description |
|---|---|---|
filter | new (...args: any[]) => ExceptionFilter<any> | The filter class to replace. |
Returns
{ useValue: (stub: Partial<ExceptionFilter<any>>) => TestingModuleBuilder }Examples
builder.overrideFilter(RateLimitedFilter).useValue({ catch: vi.fn() })overrideGuard
overrideGuard(guard: new (...args: any[]) => GuardInterface): {
useValue: (stub: Partial<GuardInterface>) => TestingModuleBuilder
}Replaces a guard with a stub wherever it applies, globally or on a controller or handler.
Parameters
| Name | Type | Description |
|---|---|---|
guard | new (...args: any[]) => GuardInterface | The guard class to replace. |
Returns
{ useValue: (stub: Partial<GuardInterface>) => TestingModuleBuilder }Examples
builder.overrideGuard(RateLimitGuard).useValue({ canActivate: () => true })overrideInterceptor Since 4.1.0
overrideInterceptor(
interceptor: new (...args: any[]) => InterceptorInterface,
): { useValue: (stub: Partial<InterceptorInterface>) => TestingModuleBuilder }Replaces an interceptor with a stub wherever it applies, globally or on a controller or handler.
The stub's intercept receives the context and next; call next.handle() to run the handler.
Parameters
| Name | Type | Description |
|---|---|---|
interceptor | new (...args: any[]) => InterceptorInterface | The interceptor class to replace. |
Returns
{ useValue: (stub: Partial<InterceptorInterface>) => TestingModuleBuilder }Examples
builder.overrideInterceptor(TimingInterceptor).useValue({ intercept: (_context, next) => next.handle() })overrideProvider
overrideProvider<T>(token: ProviderToken<T> | ServiceIdentifier<T>): {
useValue: (value: Partial<T>) => TestingModuleBuilder
}Replaces a provider with a test double.
The double needs only the members the test uses; misspelled member names are still rejected.
Parameters
| Name | Type | Description |
|---|---|---|
token | ProviderToken<T> | ServiceIdentifier<T> | The provider to replace. |
Returns
{ useValue: (value: Partial<T>) => TestingModuleBuilder }Examples
builder.overrideProvider(UserService).useValue({ findUser: vi.fn() })overrideTheme Since 4.1.0
overrideTheme(theme: ThemeOverride): TestingModuleBuilderChanges part of the app's @MeoCord({ theme }) for this module, or gives a module without an app a
theme. It goes over the app's theme, so it names only the tokens it changes; each @UseTheme, and what
themeFor looks up, still goes over it.
Parameters
| Name | Type | Description |
|---|---|---|
theme | ThemeOverride | The tokens to change, checked as |
theme.colors? | {
primary?: ColorResolvable
neutral?: ColorResolvable
success?: ColorResolvable
warning?: ColorResolvable
danger?: ColorResolvable
info?: ColorResolvable
} | Colours by role. |
theme.colors.primary? | ColorResolvable | The app's own colour, for what is neither good nor bad news. |
theme.colors.neutral? | ColorResolvable | A quiet colour, for what needs no attention. |
theme.colors.success? | ColorResolvable | Something went as asked. |
theme.colors.warning? | ColorResolvable | Something needs the user's attention, or was refused because of what they did. |
theme.colors.danger? | ColorResolvable | Something failed, a fault in the bot rather than the user. |
theme.colors.info? | ColorResolvable | Information, with no action needed. |
theme.emojis? | {
loading?: string
success?: string
warning?: string
danger?: string
info?: string
} | Emojis by role. |
theme.emojis.loading? | string | Shown while a deferred call is still working. |
theme.emojis.success? | string | Something went as asked. |
theme.emojis.warning? | string | Something needs the user's attention, or was refused because of what they did. |
theme.emojis.danger? | string | Something failed, a fault in the bot rather than the user. |
theme.emojis.info? | string | Information, with no action needed. |
theme.buttons? | {
primary?: ThemeButtonStyle
neutral?: ThemeButtonStyle
success?: ThemeButtonStyle
danger?: ThemeButtonStyle
} | Button styles by role. |
theme.buttons.primary? | ThemeButtonStyle | The action the app most expects, such as Submit. |
theme.buttons.neutral? | ThemeButtonStyle | A secondary action, such as Cancel or Back. |
theme.buttons.success? | ThemeButtonStyle | An action that confirms or approves, such as Approve. |
theme.buttons.danger? | ThemeButtonStyle | An action that removes or refuses, such as Delete or Reject. |
Returns
TestingModuleBuilderThrows
Error naming each token that is not valid.
Examples
const module = MeoCordTestingModule.create({ app: App, controllers: [ShopController] })
.overrideTheme({ colors: { primary: '#E3606D' } })
.compile()overrideThemeFor Since 4.1.0
overrideThemeFor(
resolvers:
ThemeResolvers | (new (...args: any[]) => ThemeResolver) | undefined,
): TestingModuleBuilderReplaces the app's @MeoCord({ themeFor }) for this module, or removes it with undefined. The app's
themeCache and themeForTimeoutMs still apply, and the results are cached in TestingModule.themeCache,
as the bot caches them. A mock resolver shows each lookup. A class implementing ThemeResolver is bound as
the app binds one, so overrideProvider replaces it or what it injects.
Parameters
| Name | Type | Description |
|---|---|---|
resolvers | | ThemeResolvers
| (new (...args: any[]) => ThemeResolver)
| undefined | The server's and the user's resolvers, a class implementing |
Returns
TestingModuleBuilderThrows
TypeError when a resolver is not a function, or is neither
guildnoruser, or a class has neither method.
Examples
const guild = vi.fn(() => ({ colors: { primary: '#26A042' as const } }))
const module = MeoCordTestingModule.create({ app: App, controllers: [ShopController] }).overrideThemeFor({ guild }).compile()See also
- MeoCordTestingModule
- The testing module