NestJs Testing - Is REPOSITORY part of the relevant providers/imports within module - node.js

I am trying to write the unit test cases for the mono-repo based application using nestjs.
I'm facing the following error
Nest cannot export a provider/module that is not a part of the currently processed module (CacheManagerModule). Please verify whether the exported CACHE_MANAGER_REPOSITORY is available in this particular context.
Possible Solutions:
- Is CACHE_MANAGER_REPOSITORY part of the relevant providers/imports within CacheManagerModule?
Used Packages as follows
"#nestjs/testing": "6.11.7",
"jest": "25.1.0",
"ts-jest": "25.0.0"
Please refer the sample code below
apps\api\src\modules\enduse\enduse.controller.spec.ts
import { INestApplication } from "#nestjs/common";
import { Test } from "#nestjs/testing";
import { CacheManagerModule } from "#app/cache-manager";
import * as request from "supertest";
import { EnduseController } from "./enduse.controller";
import { EnduseService } from "./enduse.service";
describe("Enduse", () => {
let app: INestApplication;
beforeAll(async () => {
const module = await Test.createTestingModule({
imports: [CacheManagerModule],
controllers: [EnduseController],
providers: [EnduseService],
}).compile();
app = module.createNestApplication();
await app.init();
});
it("/ (Get Enduse)", async () => {
const response = await request(app.getHttpServer()).get("enduse/mapping?enduse=123").expect(200);
return response;
});
});
libs\cache-manager\src\cache-manager.module.ts
import { Module } from "#nestjs/common";
import { CacheManagerService } from "./cache-manager.service";
import { CacheManagerProviders } from "./cache-manager.provider";
#Module({
providers: [CacheManagerService, ...CacheManagerProviders],
exports: [CacheManagerService, ...CacheManagerProviders],
})
export class CacheManagerModule {}
libs\cache-manager\src\cache-manager.provider.ts
import { ReviewModel } from "#app/database/models";
import { CACHEMANAGERCONST } from "./cache-manager.const";
export const CacheManagerProviders = [
{
provide: CACHEMANAGERCONST.CACHE_MANAGER_REPOSITORY,
useValue: ReviewModel,
},
];
jest.config.js
moduleNameMapper: {
"^#app/cache-manager": resolve(__dirname, "./libs/cache-manager/src")
}
How to solve the issue?
Thanks.

Related

NestJS testing that interception has been called on a controller

I'm looking to see if there is a recommended way to write a JEST test that an Interceptor has been called. In the example below LoggingInterceptor was called? The purpose of test is verify that NestJS Binding interceptors is in place.
import { Controller, Get, UseInterceptors } from '#nestjs/common';
import { AppService } from './app.service';
import { LoggingInterceptor, TransformInterceptor } from './transform.interceptor';
#Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
#UseInterceptors(LoggingInterceptor)
#Get()
getHello(): string {
return this.appService.getHello();
}
}```
I would advise against testing that. Think about what you're testing: you're testing that the framework is doing what it says it will, something that the framework itself already tests. Sure you could use the Reflect API and verify that that metadata does exist, but that's something the framework should assert, it's a feature you should just be able to use with peace of mind.
One option I used to create a Jest test to verify that a binding interceptor(Which transformed the response) on a controller was called and produced the expected response was by using NestJS Supertest lib to simulate an end to end test.
Related NestJS doc:
List item
https://docs.nestjs.com/fundamentals/testing#end-to-end-testing
Test Code Sample:
import { INestApplication } from '#nestjs/common';
import { Test } from '#nestjs/testing';
import * as request from 'supertest';
import { CatsModule } from '../../src/cats/cats.module';
import { CatsService } from '../../src/cats/cats.service';
import { CoreModule } from '../../src/core/core.module';
describe('Test interceptor response binding was triggerred', () => {
const aServiceResponse = { findAll: () => ['test'] };
const expectedResponseAfterTransformation = { code: 200, data: [ 'test' ] };
let app: INestApplication;
beforeAll(async () => {
const moduleRef = await Test.createTestingModule({
imports: [CatsModule, CoreModule],
})
.overrideProvider(CatsService)
.useValue(aServiceResponse)
.compile();
app = moduleRef.createNestApplication();
await app.init();
});
it(`/GET cats returns transformed data respsone`, () => {
return request(app.getHttpServer()).get('/cats').expect(200).expect({
data: expectedResponseAfterTransformation,
});
});
afterAll(async () => {
await app.close();
});
});

Mock nestjs decorator

I am using a custom Firewall decorator that provides some utility functionality for many of my endpoints (e.g. throttling, authorization etc.) and I want be able to mock this decorator in my endpoints:
#Controller('some')
export class SomeController {
#Firewall() // mock it and check that it's called with correct arguments
async testEndpoint() {
return 'test';
}
}
I want to mock it and check that it's called with the correct parameters, but I can't figure out how I can do this in my test cases:
import * as request from 'supertest';
import { Test } from '#nestjs/testing';
import { INestApplication } from '#nestjs/common';
import { AppModule } from 'src/app.module';
describe('Some Controller', () => {
let app: INestApplication;
beforeAll(async () => {
const moduleRef = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = moduleRef.createNestApplication();
await app.init();
});
it('some testcase', () => {
// What do I do here to mock my Firewall decorator? // <--- <--- <---
return request(app.getHttpServer()).get('/some/testEndpoint').expect(401);
});
afterAll(async () => {
await app.close();
});
});
If it can help, here is a short version of the Firewall decorator:
import { applyDecorators } from '#nestjs/common';
import { Throttle, SkipThrottle } from '#nestjs/throttler';
export function Firewall(options?: { skipThrottle?: boolean }) {
const { skipThrottle } = options || {
anonymous: false,
};
const decorators = [];
if (skipThrottle) {
decorators.push(SkipThrottle());
} else {
decorators.push(Throttle(10, 10));
}
return applyDecorators(...decorators);
}
I have checked other answers (including this one) but they didn't help.
Thanks in advance for your time!
The #Throttle() and #SkipThrottle() decorators only apply metadata to the controller / controller method they decorate. They don't do anything on their own. Your custom #Firewall() is a utility decorator to combine these into a single decorator for convenience.
If you take a look at the source code of the nestjs/throttler package you'll see it is the #ThrottlerGuard() guard that retrieves this metadata and actually does the throttling.
I suspect you configured this one as a global app guard, so it is applied for all requests.
#Module({
imports: [
ThrottlerModule.forRoot({...}),
],
providers: [
{
provide: APP_GUARD,
useClass: ThrottlerGuard,
},
],
})
export class AppModule {}
In your test you need to mock the ThrottlerGuard.
const ThrottlerGuardMock = {
canActivate(ctx) {
const request = ctx.switchToHttp().getRequest();
// mock implementation goes here
// ...
return true;
}
} as ThrottlerGuard;
const module = await Test.createTestModule({
imports: [AppModule]
})
.overrideProvider(ThrottlerGuard)
.useValue(ThrottlerGuardMock) // <-- magic happens here
.compile();
app = moduleRef.createNestApplication();
await app.init();
You could setup some spies, in the mocked guard retrieve the metadata set by the decorators applied by the #Firewall() decorator and then invoke the spies with that metadata. Then you could just verify if the spies were called with the expected values. That would verify that your custom decorator passed down the expected values to the throttle decorators. Actually testing the #ThrottleGuard() decorator is the nestjs/throttler package's responsibility.

Nestjs - Can't resolve dependencies of service

I don't know how to fix this issue. I keept trying but can't get my head arround it.
The log throws me this
Error: Nest can't resolve dependencies of the ERC20Service (?). Please make sure that the argument dependency at index [0] is available in the ERC20Module context.
import { DynamicModule, Module, Provider } from '#nestjs/common';
import { ERC20Service } from './erc20.service';
const Web3 = require("web3");
export interface ERC20ModuleOptions {
global?: boolean;
abi: Object,
wsEndpoint: string,
httpEndpoint: string
}
export const WEB3_HTTP_TOKEN = 'WEB3_HTTP_TOKEN';
export const WEB3_WS_TOKEN = 'WEB3_WS_TOKEN'
export class ERC20Module {
static forRoot(options: ERC20ModuleOptions): DynamicModule {
const httpProvider: Provider = {
provide: WEB3_HTTP_TOKEN,
useValue:new Web3(new Web3.providers.HttpProvider(options.httpEndpoint))
};
const wsProvider: Provider = {
provide: WEB3_WS_TOKEN,
useValue:new Web3(new Web3.providers.WebsocketProvider(options.wsEndpoint))
};
return {
module: ERC20Module,
providers: [httpProvider, wsProvider, ERC20Service],
exports: [ERC20Service],
global: options.global,
};
}
}
import Web3 from "web3"
import { Inject, Injectable } from '#nestjs/common';
import { WEB3_HTTP_TOKEN, WEB3_WS_TOKEN } from './erc20.module';
#Injectable()
export class ERC20Service {
constructor(#Inject(WEB3_HTTP_TOKEN) private http: Web3,#Inject(WEB3_WS_TOKEN) private ws: Web3) {
}
}
import { Module } from '#nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { ConfigModule } from '#nestjs/config';
import { ERC20Module } from 'src/modules/erc20/erc20.module';
import { abi } from 'src/config/abi';
#Module({
imports: [
ConfigModule.forRoot(),
ERC20Module.forRoot({
global: true,
httpEndpoint: `https://${process.env.CHAINSTACK_USER}:${process.env.CHAINSTACK_PASSWORD}#${process.env.CHAINSTACK_HTTP_ENDPOINT}`,
wsEndpoint: `wss://${process.env.CHAINSTACK_USER}:${process.env.CHAINSTACK_PASSWORD}#${process.env.CHAINSTACK_WS_ENDPOINT}`,
abi: abi,
}),
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
I must be blind because as far as I see it, I am doing everything the correct way when in comes to injecting, exporting and importing. Maybe someone on here can guide me along the way to solve this issue. Thank you in advance
Your erc20.service and erc20.module files import from each other, creating a circular dependency. Move the constants to a separate file and you should be good to go
// erc20.constants.ts
export const WEB3_HTTP_TOKEN = 'WEB3_HTTP_TOKEN';
export const WEB3_WS_TOKEN = 'WEB3_WS_TOKEN'
// erc20.module.ts
import { DynamicModule, Module, Provider } from '#nestjs/common';
import { WEB3_HTTP_TOKEN, WEB3_WS_TOKEN } from './erc20.constants';
import { ERC20Service } from './erc20.service';
const Web3 = require("web3");
export interface ERC20ModuleOptions {
global?: boolean;
abi: Object,
wsEndpoint: string,
httpEndpoint: string
}
export class ERC20Module {
static forRoot(options: ERC20ModuleOptions): DynamicModule {
const httpProvider: Provider = {
provide: WEB3_HTTP_TOKEN,
useValue:new Web3(new Web3.providers.HttpProvider(options.httpEndpoint))
};
const wsProvider: Provider = {
provide: WEB3_WS_TOKEN,
useValue:new Web3(new Web3.providers.WebsocketProvider(options.wsEndpoint))
};
return {
module: ERC20Module,
providers: [httpProvider, wsProvider, ERC20Service],
exports: [ERC20Service],
global: options.global,
};
}
}
// erc20.service.ts
import Web3 from "web3"
import { Inject, Injectable } from '#nestjs/common';
import { WEB3_HTTP_TOKEN, WEB3_WS_TOKEN } from './erc20.constants';
#Injectable()
export class ERC20Service {
constructor(#Inject(WEB3_HTTP_TOKEN) private http: Web3,#Inject(WEB3_WS_TOKEN) private ws: Web3) {
}
}

process.env's are undefined - NestJS

I've decided to write here because I've ran out of ideas. I have a NestJS app in which I use env's - nothing unusual. But something strange happens when I want to use them. I also have my own parser of these values which returns them in a convenient object - that's the first file:
env.ts
const parseStringEnv = (name: string) => {
const value: string = process.env[name];
if (!value) {
throw new Error(`Invalid env ${name}`);
}
return value;
};
const parseIntEnv = (name: string) => {
const value: string = process.env[name];
const int: number = parseInt(value);
if (isNaN(int)) {
throw new Error(`Invalid env ${name}`);
}
return int;
};
const parseBoolEnv = (name: string) => {
const value: string = process.env[name];
if (value === "false") {
return false;
}
if (value === "true") {
return true;
}
throw new Error(`Invalid env ${name}`);
};
const parseMongoString = (): string => {
const host = parseStringEnv("DATABASE_HOST");
const port = parseStringEnv("DATABASE_PORT");
const user = parseStringEnv("DATABASE_USER");
const pwd = parseStringEnv("DATABASE_PWD");
const dbname = parseStringEnv("DATABASE_NAME");
return `mongodb://${user}:${pwd}#${host}:${port}/${dbname}?authSource=admin&ssl=false`;
};
export const env = {
JWT_SECRET: parseStringEnv("JWT_SECRET"),
PORT_BACKEND: parseIntEnv("PORT_BACKEND"),
CLIENT_HOST: parseStringEnv("CLIENT_HOST"),
ENABLE_CORS: parseBoolEnv("ENABLE_CORS"),
MONGO_URI: parseMongoString(),
};
export type Env = typeof env;
I want to use it for setting port on which the app runs on and also the connection parameters for Mongoose:
In main.ts:
<rest of the code>
await app.listen(env.PORT_BACKEND || 8080);
<rest of the code>
Now, the magic starts here - the app starts just fine when ONLY ConfigModule is being imported. It will also start without ConfigModule and with require('doting').config() added. When I add MongooseModule, the app crashes because it can't parse env - and the best thing is that exception thrown has nothing to do with env's that are used to create MONGO_URI!! I'm getting "Invalid env JWT_SECRET" from my parser.
In app.module.ts
import { Module } from "#nestjs/common";
import { ConfigModule } from "#nestjs/config";
import { MongooseModule } from "#nestjs/mongoose";
import { AppController } from "./app.controller";
import { env } from "./common/env";
#Module({
imports: [
ConfigModule.forRoot({
isGlobal: true,
}),
MongooseModule.forRoot(env.MONGO_URI), //WTF?
],
controllers: [AppController],
})
export class AppModule {}
I've honestly just ran out of ideas what could be wrong. The parser worked just fine in my last project (but I haven't used Mongoose so maybe that's what causes issues). Below is my .env file template.
JWT_SECRET=
ENABLE_CORS=
PORT_BACKEND=
DATABASE_HOST=
DATABASE_PORT=
DATABASE_USER=
DATABASE_PWD
DATABASE_NAME=
CLIENT_HOST=
Thanks for everyone who has spent their time trying to help me ;)
What's happening is you're importing env.ts before the ConfigModule has imported and set the variables in your .env file.
This is why calling require('dotenv').config() works. Under the hood, that's what the ConfigModule is doing for you. However, your call to ConfigModule.forRoot is happening after you import env.ts, so the .env file hasn't been imported yet and those variables don't yet exist.
I would highly recommend you take a look at custom configuration files, which handles this for you the "Nest way":
From the Nest docs, but note that you could also use the env.ts file you already have:
// env.ts
export default () => ({
// Add your own properties here however you'd like
port: parseInt(process.env.PORT, 10) || 3000,
database: {
host: process.env.DATABASE_HOST,
port: parseInt(process.env.DATABASE_PORT, 10) || 5432
}
});
And then modify your AppModule to the following. Note that we're using the forRootAsync so that we can get a handle to the ConfigService and grab the variable from that.
// app.module.ts
import configuration from './common/env';
#Module({
imports: [
ConfigModule.forRoot({
load: [configuration],
}),
//
MongooseModule.forRootAsync({
imports: [ConfigModule],
useFactory: async (configService: ConfigService) => ({
uri: configService.get<string>('MONGO_URI'),
}),
inject: [ConfigService],
});
],
})
export class AppModule {}
As an alternative, you could also just call require('dotenv').config() inside your env.ts file at the top, but you'll miss out on all the ConfigModule helpers like dev/prod .env files.
By using registerAsync of JWT module and read process.env inside useFactory method worked for me
#Module({
imports: [
JwtModule.registerAsync({
useFactory: () => ({
secret: process.env.JWT_SECRET_KEY,
signOptions: { expiresIn: 3600 },
}),
})
],
controllers: [AppController],
})
In my case just need to replace the order import module.
import { Module } from "#nestjs/common";
import { ConfigModule } from "#nestjs/config";
import { MongooseModule } from "#nestjs/mongoose";
import { AppController } from "./app.controller";
import { env } from "./common/env"; // call process.env.xxx here > undefined
#Module({
imports: [
ConfigModule.forRoot({
isGlobal: true,
}), // process.env.xxx must be called after this line
MongooseModule.forRoot(env.MONGO_URI),
],
controllers: [AppController],
})
export class AppModule {}
so fix
import { Module } from "#nestjs/common";
import { ConfigModule } from "#nestjs/config";
// should place this at very first line
const envModule = ConfigModule.forRoot({
isGlobal: true,
})
import { MongooseModule } from "#nestjs/mongoose";
import { AppController } from "./app.controller";
import { env } from "./common/env";
#Module({
imports: [
envModule,
MongooseModule.forRoot(env.MONGO_URI),
],
controllers: [AppController],
})
export class AppModule {}
In my case I downgraded #types/node to be the same version as my node version. Could be a hint.

Dependency injection - app.get(UploadService) on INestApplication doesn't resolve its dependencies

I am trying to create external CLI, which uses my Nest context. I've made entry-point, which creates app with NestFactory.create. After accessing service with app.get, the service exists and works itself. The problem is, that it doesn't resolve any of its dependencies. There is no example on Docs and neither have I found anything related to this issue on Internet.
I am using the newest version of Nest.js on Node 10 as on 17.07.2019.
main.ts
import { NestFactory } from '#nestjs/core';
import { AppModule } from '../app.module';
import { UploadService } from 'src/api/upload/upload.service';
import { UploadModule } from 'src/api/upload/upload.module';
async function bootstrap(): Promise<void> {
const app = await NestFactory.create(AppModule);
await app.init();
const service: UploadService = app.select(UploadModule).get(UploadService);
console.log(service); // OK - returns UploadService instance
console.log(service.uploadModel); // X - returns `undefined`
console.log(service.configService); // X - returns `undefined`
}
bootstrap();
app.module.ts
import { Module } from '#nestjs/common';
import { MongooseModule } from '#nestjs/mongoose';
import { ConfigModule } from './config/config.module';
import { ConfigService } from './config/config.service';
import { AwsModule } from './services/aws/aws.module';
import { UploadModule } from './api/upload/upload.module';
#Module({
imports: [
ConfigModule,
MongooseModule.forRootAsync({
useFactory: async (configService: ConfigService): Promise<object> => ({
uri: configService.data.database.mongo.uri,
useCreateIndex: true,
useNewUrlParser: true,
}),
inject: [ConfigService],
}),
AwsModule,
UploadModule,
],
})
export class AppModule {}
upload.module.ts
import { Module } from '#nestjs/common';
import { MongooseModule } from '#nestjs/mongoose';
import { AwsModule } from '../../services/aws/aws.module';
import { UploadService } from './upload.service';
import { UploadSchema } from './upload.schema';
#Module({
imports: [MongooseModule.forFeature([{ name: 'Upload', schema: UploadSchema }]), AwsModule],
providers: [UploadService],
exports: [UploadService],
})
export class UploadModule {}
upload.service.ts
import { Injectable } from '#nestjs/common';
import { AwsService } from '../../services/aws/aws.service';
import { Upload } from './upload.schema';
import { InjectModel } from '#nestjs/mongoose';
import { Model } from 'mongoose';
#Injectable()
export class UploadService {
constructor(
#InjectModel('Upload')
readonly uploadModel: Model<Upload>,
readonly awsService: AwsService,
) {}
}
Expected outputs of
console.log(service.uploadModel); // X - returns `undefined`
console.log(service.configService); // X - returns `undefined`
are model/service instances. Unfortunately they return both undefined, as the dependency-injection doesn't take a place.
I tried what You did, and in nestjs version: 7.6 it works as expected.
So my assumption is they fixed it in meantime.
I had the same issue. The docs are actually pretty clear about this: https://docs.nestjs.com/standalone-applications
But I actually run into the same problem and I dont think this an intended behaviour. I tried removing a dependency after another to see if something changes.
I had request scoped services injected. As long there was an "unresolvable dependency" (request-scoped ones) all injected dependencies were undefined. I'd expect a "dependency cannot be resolved" error but it just silently failed.
See also https://github.com/nestjs/nest/issues/4630
Make sure all your dependencies can be created successfully within the correct scope for standalone apps.
To get a service with for example request-scoped dependencies use module.resolve(Service) instead of get.

Resources