How to mock PrismaService for e2e in NestJs? - jestjs

I'm usign nestjs-prisma in NestJs and I have in the following import for the prisma module in the app.module, so in every service I can use the PrismaService as the library allows it.
app.module.ts
import { Module } from '#nestjs/common';
import { PrismaModule } from 'nestjs-prisma';
import { CategoryModule } from './category/category.module';
#Module({
imports: [PrismaModule.forRoot({ isGlobal: true }), CategoryModule],
})
export class AppModule {}
I want to do a e2e test just for the CategoryModule and need to mock the PrismaService, how can i do that?

You need to mook first of all the PrismaModule and then import in it the PrismaService, here is an example.
category.e2e-spec.ts
import { Test, TestingModule } from '#nestjs/testing';
import { INestApplication } from '#nestjs/common';
import * as request from 'supertest';
import { PrismaModule, PrismaService } from 'nestjs-prisma';
import { CategoryModule } from '../src/category/category.module';
describe('CategoryModule (e2e)', () => {
let app: INestApplication;
beforeEach(async () => {
// Here you have to mock the category funtions that you'll use
const mockPrismaService = {
provide: PrismaService,
useFactory: () => ({
category: {
findMany: jest.fn(() => [])
},
}),
};
// Here is the creation of the module and the definition for the service
const mockPrismaModule = {
module: PrismaModule,
providers: [mockPrismaService],
global: true,
};
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [CategoryModule, mockPrismaModule], // Here is the import for the mock module
}).compile();
app = moduleFixture.createNestApplication();
await app.init();
});
it('(GET) /category', () => {
return request(app.getHttpServer()).get('/category').expect(200);
});
});

Related

Mocking a MongooseModule import in Nestjs tests

I'm experimenting with NestJs, following a tutorial. I've decided I want to write some tests on the code I've written.
I'm writing tests for the ProductController and the test file for the controller mocks the ProductModule to which the controller belongs. My ProductModule imports MongooseModule and presumably I need to mock that import in the TestingModule of the controller test file.
How can I go about mocking that import?
ProductModule;
import { Module } from '#nestjs/common';
import { ProductController } from './product.controller';
import { ProductService } from './product.service';
import { MongooseModule } from '#nestjs/mongoose';
import { ProductSchema } from './schemas/product.schema';
#Module({
imports: [
MongooseModule.forFeature([{ name: 'Product', schema: ProductSchema }]),
],
providers: [ProductService],
controllers: [ProductController],
})
export class ProductModule {}
ProductController test file;
import { Test, TestingModule } from '#nestjs/testing';
import { ProductController } from './product.controller';
import { ProductService } from './product.service';
describe('ProductController', () => {
let controller: ProductController;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [ProductService],
controllers: [ProductController],
}).compile();
controller = module.get<ProductController>(ProductController);
});
it('should be defined', () => {
expect(controller).toBeDefined();
});
});
For the ProductController test I'd use a custom provider like
{
provide: ProductService,
useValue: {
method1: jest.fn(),
methodN: jest.fn()
}
}
For your ProductService test I'd mock the #InjectModel('Product') by using getModelToken() and a custom provider like
{
provide: getModelToken('Product'),
useValue: {
productModelMethod1: jest.fn(),
}
}
You can see a repo here with a bunch of test examples

NestJS unitesting Middleware

I'm trying to unit test a middleware to avoid sending a request but I'm actually not able to retreive the middleware with .get
import { NestApplicationContext } from '#nestjs/core';
import { BodyParserMiddleware } from '../../../src/config/body-parser/body-parser.middleware';
import { FeaturesModule } from '../../../src/features/features.module';
describe('CatsController', () => {
let app: NestApplicationContext;
beforeAll(() => {
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = moduleFixture.createNestApplication();
});
describe('findAll', () => {
it('should return an array of cats', async () => {
console.log(app.get(BodyParserMiddleware));
});
});
});
my app module looks like this
import { Module } from '#nestjs/common';
import { FeaturesModule } from './features/features.module';
#Module({
imports: [FeaturesModule],
})
export class AppModule {}
and the last one feature module
import { MiddlewareConsumer, Module, NestModule } from '#nestjs/common';
import { BodyParserMiddleware } from '../config/body-parser/body-parser.middleware';
#Module({
imports: [
AuthModule,
],
})
export class FeaturesModule implements NestModule {
configure(consumer: MiddlewareConsumer): void {
consumer.apply(BodyParserMiddleware).forRoutes('api/v*/*/get-many');
}
}
Nest could not find BodyParserMiddleware element (this provider does not exist in the current context)
this is the error that im having
anyone knows how to retreive the middleware from the app container?
Also when I try to get the Features module to see if there is any clue there where I can go I receive
FeaturesModule {}

How to mock configService as registerAs in my unit tests in nestjs

Hi I currently want to mock my configService injected as RegisterAs which allows me to have strong typing when accessing my environment variables, my problem with this is that when I mock the configService in my unit test and try to manipulate the environment values ​​it doesn't I'm making it.
I show some of what I have.
My Nestjs Project:
src/
|- config/
| |- environment.ts
|- modules/
| |- auth/
| |- auth.service.ts
| |- auth.service.spec.ts
|- app.module.ts
|- main.ts
|+- ...
app.module.ts
import * as Joi from 'joi';
import { AuthModule } from './modules/auth/auth.module';
import { ConfigModule } from '#nestjs/config';
import { Module } from '#nestjs/common';
import { ScheduleModule } from '#nestjs/schedule';
import env from './config/environment';
#Module({
imports: [
ScheduleModule.forRoot(),
ConfigModule.forRoot({ // <= I load the variables and validate them with Joi
isGlobal: true,
load: [env],
validationSchema: Joi.object().keys({
JWT_SESSION: Joi.string().trim(),
JWT_SESSION_EXP: Joi.number(),
}),
}),
AuthModule,
],
})
export class AppModule {}
config/environment.ts
import { registerAs } from '#nestjs/config';
export default registerAs('env', () => ({
jwtSession: process.env.JWT_SESSION || undefined,
expSession: Number(process.env.JWT_SESSION_EXP) || undefined,
}));
auth.service.ts
import * as moment from 'moment';
import { ConfigType } from '#nestjs/config';
import { Inject, Injectable, Logger } from '#nestjs/common';
import { AuthResponse } from './interfaces/login.interface';
import { AuthenticationDto } from './dto/auth.dto';
import { HttpService } from '#nestjs/axios';
import { JwtFidelity } from './interfaces/jwt-fidelity.interface';
import env from './../../config/environment';
import jwt_decode from 'jwt-decode';
import { lastValueFrom } from 'rxjs';
#Injectable()
export class AuthService {
constructor(
#Inject(env.KEY) private configService: ConfigType<typeof env>, // <= I inject the environment variables like this
) {}
isTokenExpired(): boolean { // <= This checks if the token has expired
if (!this.configService.jwtSession) return true;
if (!this.configService.expSession) return true;
return false;
}
}
auth.service.spec.ts
Note: this is where i would like you to help me.
import { ConfigModule, ConfigService } from '#nestjs/config';
import { HttpModule, HttpService } from '#nestjs/axios';
import { Test, TestingModule } from '#nestjs/testing';
import { AuthService } from '../auth.service';
import environment from '../../../config/environment';
import { of } from 'rxjs';
const env = () => ({
auth: {
partner: {
jwtSeesion:
'jwt-token',
expSession: '1661878302',
},
},
});
describe('AuthService', () => {
let service: AuthService;
let httpService: HttpService;
let configService: ConfigService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
imports: [HttpModule, ConfigModule.forRoot({ load: [environment] })],
providers: [
HttpService,
AuthService,
ConfigService,
{ provide: 'CONFIGURATION(env)', useFactory: env },
],
})
.overrideProvider(HttpService)
.useValue({ request: jest.fn() })
.overrideProvider(ConfigService)
.useValue({ jwtSession: jest.fn() })
.compile();
service = module.get<AuthService>(AuthService);
httpService = module.get<HttpService>(HttpService);
configService = module.get<ConfigService>(ConfigService);
});
it('should be defined', () => {
expect(service).toBeDefined();
expect(httpService).toBeDefined();
expect(configService).toBeDefined();
});
describe('AuthController.isTokenExpired', () => {
it('variable jwtSession undefined', async () => {
configService.jwtSession = undefined; // <= I would like the possibility of doing something like that
jest.spyOn(configService, 'jwtSession').mockReturnValue('hola' as any); // <= Or something like that
const isExpired = await service.isTokenExpired();
expect(isExpired).toBeTruthy();
});
});
});
I would like that at the level of each test I can be able to manipulate the value of the environment variables that I have injected with #Inject(env.KEY) private configService: ConfigType<typeof env>, in my auth.service.ts

NestJS Test No Repository was found

I have a NestJS project configured with TypeORM. The e2e test is the one that is generated by the CLI:
import { Test, TestingModule } from '#nestjs/testing';
import { INestApplication } from '#nestjs/common';
import * as request from 'supertest';
import { AppModule } from './../src/app.module';
describe('AppController (e2e)', () => {
let app: INestApplication;
beforeEach(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = moduleFixture.createNestApplication();
await app.init();
});
afterAll(async () => {
await app.close();
});
it('/ (GET)', () => {
return request(app.getHttpServer())
.get('/')
.expect(200)
.expect('Hello World!');
});
});
The error I get is the following:
RepositoryNotFoundError: No repository for "Question" was found. Looks like this entity is not registered in current "default" connection?
at RepositoryNotFoundError.TypeORMError [as constructor] (../src/error/TypeORMError.ts:7:9)
at new RepositoryNotFoundError (../src/error/RepositoryNotFoundError.ts:10:9)
at EntityManager.Object.<anonymous>.EntityManager.getRepository (../src/entity-manager/EntityManager.ts:975:19)
at Connection.Object.<anonymous>.Connection.getRepository (../src/connection/Connection.ts:354:29)
at InstanceWrapper.useFactory [as metatype] (../node_modules/#nestjs/typeorm/dist/typeorm.providers.js:17:30)
at TestingInjector.instantiateClass (../node_modules/#nestjs/core/injector/injector.js:304:55)
at callback (../node_modules/#nestjs/core/injector/injector.js:48:41)
at TestingInjector.resolveConstructorParams (../node_modules/#nestjs/core/injector/injector.js:124:24)
at TestingInjector.loadInstance (../node_modules/#nestjs/core/injector/injector.js:52:9)
AppModule is configured like this:
import { Module } from '#nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { TypeOrmModule } from '#nestjs/typeorm';
import { QuestionsModule } from './questions/questions.module';
import { Connection, getConnectionOptions } from 'typeorm';
#Module({
imports: [
// docker run --name postgres_questions_answers -e POSTGRES_PASSWORD=postgres -e POSTGRES_USER=postgres -e POSTGRES_DB=dev_questions_answers -p 5432:5432 -d postgres
TypeOrmModule.forRootAsync({
useFactory: async () =>
Object.assign(await getConnectionOptions(), {
// FIXME: This is done so Jest e2e doesnt raise Cannot create a new connection named "default", because connection with such name already exist and it now has an active connection session.
keepConnectionAlive: process.env.NODE_ENV === 'test',
}),
}),
QuestionsModule,
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {
constructor(private connection: Connection) {}
}
And the QuestionsModule like this:
import { Module } from '#nestjs/common';
import { TypeOrmModule } from '#nestjs/typeorm';
import { QuestionsService } from './questions.service';
import { QuestionsController } from './questions.controller';
import { Question } from '../entities/question.entity';
#Module({
imports: [TypeOrmModule.forFeature([Question])],
controllers: [QuestionsController],
providers: [QuestionsService],
})
export class QuestionsModule {}
Project runs fine in development and production mode, so I am wondering what am I doing wrong here?

How to apply Global Pipes during e2e tests

How do you apply global pipes when using Test.createTestingModule?
Normally, global pipes are added when the application is mounted in main.ts.
beforeEach(async done => {
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [AppModule]
}).compile()
app = moduleFixture.createNestApplication()
await app.init()
done()
})
Here's what I do to ensure that the global pipes in my main.ts file are always in sync with my testing setup...
Start by creating a new file called main.config.ts, this should contain your global pipes, filters, etc:
import { INestApplication, ValidationPipe } from "#nestjs/common";
export function mainConfig(app: INestApplication) {
app.enableCors();
app.useGlobalPipes(new ValidationPipe());
}
Next, use the newly created mainConfig function in both the main.ts and app.e2e-spec.ts (or wherever you setup your tests):
main.ts
import { NestFactory } from "#nestjs/core";
import { mainConfig } from "main.config";
import { AppModule } from "./app.module";
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// use here
mainConfig(app);
await app.listen(3000);
}
bootstrap();
app.e2e-spec.ts
import { INestApplication } from "#nestjs/common";
import { TestingModule, Test } from "#nestjs/testing";
import { AppModule } from "app.module";
import { mainConfig } from "main.config";
let app: INestApplication;
beforeEach(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = moduleFixture.createNestApplication();
// use here
mainConfig(app);
await app.init();
});
Now if you need to add a new pipe, you'll only have to make the change in one spot (main.config.ts) to see it reflected in both the app and the tests.
You can add them before you initialize the testing module:
beforeEach(async done => {
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [AppModule]
}).compile()
app = moduleFixture.createNestApplication()
// Add global pipe here
app.useGlobalPipes(new ValidationPipe({ transform: true, whitelist: true, forbidNonWhitelisted: true }))
await app.init()
done()
})
Another approach is to declare the global pipes in the module using APP_PIPE.
#Module({
providers: [
{
provide: APP_PIPE,
useClass: ValidationPipe,
},
],
})
export class AppModule {}
Then it will be available in your e2e tests.

Resources