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.
Related
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
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 {}
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);
});
});
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?
I am running into the issue Error querying the database: db error: FATAL: sorry, too many clients already and I am convinced it is because a new instance of the app is being instantiated for every test suite. I have attempted to break the app creation out into a helper file, and that file looks as follows
import { INestApplication } from '#nestjs/common';
import { Test, TestingModule } from '#nestjs/testing';
import { AppModule } from '../../src/app.module';
import { PrismaService } from '../../src/prisma.service';
declare global {
var app: INestApplication | undefined;
}
export const getApp = async () => {
if (global.app) {
return global.app;
}
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [AppModule],
providers: [PrismaService],
}).compile();
const app = moduleFixture.createNestApplication();
await app.init();
global.app = app;
return app;
};
This however does not work, when I add console logs, I can see that app is being instantiated for every test suite.
This is how my typical before hook looks
beforeAll(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = moduleFixture.createNestApplication();
await app.init();
});
I had the same issue and solved it by using Jest's globalSetup and globalTeardown options. I create one instance of the app and seed the DB before running the tests, and destroy it and run the teardown when they've finished. I use a global variable to reference the app instance.
In my jest-e2e.json:
{
...
"globalSetup": "<rootDir>/test/test-setup.ts",
"globalTeardown": "<rootDir>/test/test-teardown.ts"
}
test-setup.ts:
import * as fs from 'fs';
import { FastifyAdapter, NestFastifyApplication } from '#nestjs/platform-fastify';
import { Test } from '#nestjs/testing';
import { AppModule } from './../src/app.module';
import { WriteDataSource } from './../src/database/database.module';
module.exports = async () => {
const moduleRef = await Test.createTestingModule({
imports: [ AppModule ]
})
.compile();
global.app = moduleRef.createNestApplication<NestFastifyApplication>(
new FastifyAdapter()
);
await global.app.init();
await global.app.getHttpAdapter().getInstance().ready();
const seedQuery = fs.readFileSync(__dirname + '/scripts/database-seed.sql', { encoding: 'utf-8' });
await WriteDataSource.manager.query(seedQuery);
};
test-teardown.ts:
import * as fs from 'fs';
import { WriteDataSource } from '../src/database/database.module';
module.exports = async () => {
const teardownQuery = fs.readFileSync(__dirname + '/scripts/database-teardown.sql', { encoding: 'utf-8' }).replace(/\n/g, '');
await WriteDataSource.query(teardownQuery);
await WriteDataSource.destroy();
await global.app.close();
};
I solved it by exporting the server instance and everything else i needed:
let server: Server
let app: INestApplication
let testService: TestService
export const initServer = async () => {
const module = Test.createTestingModule({
imports: [AppModule, TestModule]
})
const testModule = await module.compile()
app = testModule.createNestApplication()
app.useGlobalPipes(
new ValidationPipe({
whitelist: true
})
)
server = app.getHttpServer()
testService = app.get(TestService)
await app.init()
}
export { app, server, testService }
Here i exported the function to clear all my databases to use where needed:
export const clearAllDatabases = async () => {
models.map(async (model: any) => {
await model.destroy({
where: {},
truncate: true,
cascade: true
})
})
}
import { app, initServer } from '#tests/resources/config/test-server'
import { clearAllDatabases } from '#tests/resources/config/clear-databases'
export const setupTestData = async () => {
await initServer()
await clearAllDatabases()
await userRegister()
await app.close()
}