Is it possible to use ClassSerializeInterceptor while testing Nest.JS controllers?
Our issue is that the ClassSerializeInterceptor works fine as part of the app but does not run when the controller is instantiated as part of a unit test. I tried providing ClassSerializeInterdeptor as part of the Testing Module but no luck.
Example code:
Test
describe('AppController', () => {
let appController: AppController;
beforeEach(async () => {
const app: TestingModule = await Test.createTestingModule({
controllers: [AppController],
providers: [AppService, ClassSerializerInterceptor],
}).compile();
appController = app.get<AppController>(AppController);
});
describe('root', () => {
it('should not expose exlcuded fields"', async () => {
// expect(appController.getHello()).toBe('Hello World!');
const s = await appController.getHello();
expect(s).toHaveProperty('shouldBeIncluded');
expect(s).not.toHaveProperty('shouldBeRemoved');
});
});
});
Test Entity:
#ObjectType()
#Entity()
export class TestEntity {
#Field(type => ID)
#PrimaryGeneratedColumn('uuid')
shouldBeIncluded: string;
#Column({ nullable: true })
#Exclude()
shouldBeRemoved: string;
}
Testing interceptors as a part of the request flow can only be done in e2e and partial integration test. You'll need to set up a supertest instance as described in the docs and send in the request to ensure that the ClassSerializeInterceptor is running as you expect it to.
You can use:
import { createMock } from '#golevelup/ts-jest';
import { Test } from '#nestjs/testing';
import { Reflector } from '#nestjs/core';
import {
CallHandler,
ClassSerializerInterceptor,
ExecutionContext,
} from '#nestjs/common';
const executionContext = createMock<ExecutionContext>();
const callHandler = createMock<CallHandler>();
const app = await Test.createTestingModule({}).compile();
const reflector = app.get(Reflector);
const serializer = new ClassSerializerInterceptor(reflector, {
strategy: 'excludeAll',
});
callHandler.handle.mockReturnValueOnce(from(['Hello world']));
const actualResult = await lastValueFrom(serializer.intercept(executionContext, callHandler));
expect(actualResult).toEqual('Hello world')
Related
auth.controller file
`import { Test, TestingModule } from '#nestjs/testing';
import { AuthController } from './auth.controller';
describe('AuthController', () => {
let controller: AuthController;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [AuthController],
}).compile();
controller = module.get<AuthController>(AuthController);
});
it('should be defined', () => {
expect(controller).toBeDefined();
});
});`
auth.service file
`import { Injectable } from '#nestjs/common';
#Injectable({})
export class AuthService {
signin()
{
return { msg: 'i am signin' };
}
signup()
{
return { msg: 'i am signup' };
}
}`
i am beginner level and getting this type of error even if remove all the code to simple return message my vcode gives same error what can be the issue
I'm surprised you're not getting a Dependency Injection error related to a missing AuthService dependency. You need to provide a custom provider for the AuthService to inject into the AuthController for the purpose of testing.
This repository has a lot of testing examples to take a look at
Im new to NestJS and im trying to setup my end to end testing. It does not throw any errors but the request always returns 404.
The test look like this:
import { CreateProductDto } from './../src/products/dto/create-product.dto';
import { ProductsModule } from './../src/products/products.module';
import { Product } from './../src/products/entities/product.entity';
import { Repository } from 'typeorm';
import { INestApplication } from '#nestjs/common';
import * as request from 'supertest';
import { createTypeOrmConfig } from './utils';
import { Test, TestingModule } from '#nestjs/testing';
import { TypeOrmModule } from '#nestjs/typeorm';
describe('ProductsController (e2e)', () => {
let app: INestApplication;
let productRepository: Repository<Product>;
beforeEach(async () => {
const config = createTypeOrmConfig();
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [
TypeOrmModule.forRoot(config),
TypeOrmModule.forFeature([Product]),
ProductsModule,
],
}).compile();
app = moduleFixture.createNestApplication();
productRepository = moduleFixture.get('ProductRepository');
await app.init();
});
afterAll(async () => {
await app.close();
});
it('create', async () => {
// Arrange
const createProductDto = new CreateProductDto();
createProductDto.name = 'testname';
createProductDto.description = 'testdescription';
createProductDto.price = 120;
createProductDto.image = 'testimage';
// Act
const response = await request(app.getHttpServer())
.post(`/api/v1/products`)
.send(createProductDto);
// Assert
expect(response.status).toEqual(201);
});
});
I expected it to returns a 201 response. I tried more routes and same as before.
Do you have #Controller('api/v1/products') on your ProductsController? If not, you don't ever configure the test application to be served from /api/v1, you'd need to set the global prefix and enable versioning (assuming you usually do these in your main.ts). For a simple fix, remove the /api/v1 from your .post() method.
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()
}
I have been trying for a long time and nothing works, how could I test the "compare" function inside the "methodName" method?
teste.spec.ts
import { Test, TestingModule } from '#nestjs/testing';
import { TesteService } from './teste.service';
describe('TesteService', () => {
let service: TesteService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [TesteService],
}).compile();
service = module.get<TesteService>(TesteService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
it('methodName need return a string', () => {
expect(service.methodName()).toEqual(typeof String)
})
});
teste.ts
import { Injectable } from '#nestjs/common';
import { compare } from 'bcrypt'
#Injectable()
export class TesteService {
methodName() {
const password = '123456789'
const checkPassword = compare('123456789', password)
return checkPassword ? 'correct' : 'wrong'
}
}
if i do it this way would it be okay?
it('compare password', () => {
const checkPassword = compare('123456789', '123456789')
expect(checkPassword).toBeTruthy()
})
As a principle in unit testing, we assume that external packages have been tested and are working. What you can do with your test, though, is to spy on the compare function and check weather your method is calling it and what it's calling with.
import * as bcrypt from 'bcrypt';
it('should call compare', () => {
const spyCompare = jest.spyOn(bcrypt, 'compare');
service.methodName();
expect(spyCompare).toHaveBeenCalled();
expect(spyCompare).toHaveBeenCalledWith('123456789');
})
I try to introduce e2e tests for my simple NestJS backend services. I am providing a custom userService and a custom UserRepository mocked with sinon.
This is my user.e2e-spec.ts file:
import * as request from 'supertest';
import * as sinon from 'sinon';
import { Test } from '#nestjs/testing';
import { INestApplication } from '#nestjs/common';
import { UserService } from '../../src/user/user.service';
import { getRepositoryToken } from '#nestjs/typeorm';
import { User } from '../../src/user/user.entity';
import { TestUtil } from '../../src/utils/TestUtil';
import { createFakeUser } from '../../src/user/test/userTestUtil';
let sandbox: sinon.SinonSandbox;
let testUtil;
describe('User', () => {
let app: INestApplication;
const fakeUser = createFakeUser();
const userService = { findOne: () => fakeUser };
beforeAll(async () => {
sandbox = sinon.createSandbox();
testUtil = new TestUtil(sandbox);
const module = await Test.createTestingModule({
providers: [
{
provide: UserService,
useValue: userService,
},
{
provide: getRepositoryToken(User),
useValue: testUtil.getMockRepository().object,
},
],
}).compile();
app = module.createNestApplication();
await app.init();
});
it(`/GET user`, () => {
return request(app.getHttpServer())
.get('/user/:id')
.expect(200)
.expect({
data: userService.findOne(),
});
});
afterAll(async () => {
await app.close();
});
});
and this is my user.controller.ts:
import { ApiBearerAuth, ApiUseTags } from '#nestjs/swagger';
import { Controller, Get, Param } from '#nestjs/common';
import { UserService } from './user.service';
import { User } from './user.entity';
#ApiUseTags('Users')
#ApiBearerAuth()
#Controller('user')
export class UserController {
constructor(private readonly userService: UserService) {}
#Get('/:id')
findOne(#Param('id') id: number): Promise<User> {
return this.userService.find(id);
}
}
I wrote a bunch of Unit tests with the same pattern and it works. Have no clue what is wrong with this e2e supertest.
Thanks for your help!
UPDATE:
This is the error message I get:
TypeError: request is not a function
at Object.it (/Users/florian/Development/Houzy/nestjs-backend/e2e/user/user.e2e-spec.ts:40:16)
at Object.asyncFn (/Users/florian/Development/Houzy/nestjs-backend/node_modules/jest-jasmine2/build/jasmine_async.js:124:345)
at resolve (/Users/florian/Development/Houzy/nestjs-backend/node_modules/jest-jasmine2/build/queue_runner.js:46:12)
at new Promise (<anonymous>)
at mapper (/Users/florian/Development/Houzy/nestjs-backend/node_modules/jest-jasmine2/build/queue_runner.js:34:499)
at promise.then (/Users/florian/Development/Houzy/nestjs-backend/node_modules/jest-jasmine2/build/queue_runner.js:74:39)
at <anonymous>
Change import of request to:
import request from 'supertest';
In your test, replace :id with number:
it(`/GET user`, () => {
return request(app.getHttpServer())
.get('/user/1') // pass here id, not a string
.expect(200)
.expect({
data: userService.findOne(),
});
});
And in controller:
#Get('/:id')
findOne(#Param('id') id: number): Promise<User> {
return this.userService.find(id);
}
This should work now.