I want to implement unit testing for guards using nest js(Jest JS). I could nt find much documentation.
import {Test, TestingModule} from '#nestjs/testing';
import {CatsController} from '../src/modules/cats/cats.controller';
import {CatsService} from '../src/modules/cats/cats.service';
import {ICat} from '../src/modules/cats/interfaces/ICat';
import {JwtStrategy} from '../src/strategy/AppId.strategy';
beforeEach(async () => {
const module = await Test.createTestingModule({
controllers: [CatsController],
providers: [CatsService],
}).compile();
catsService = module.get<CatsService>(CatsService);
catsController = module.get<CatsController>(CatsController);
});
describe('findAll', () => {
it('should return an array of cats', async () => {
// const appIdAuthContext: AppIDAuthToken = tokenInfo;
const result: ICat = {
name: 'test',
age: 1,
breed: 'one'
};
jest.spyOn(catsService, 'findAll').mockImplementation(() => result);
console.log(result);
console.log(catsController.findAll());
expect(await catsController.findAll()).toBe(result);
});
});
The above code will return array of cats. i want to implement guards for this unit test.
I have found an implementation.i'm sharing the sample , it may help somebody
function createTestModule() {
return Test.createTestingModule({
imports: [
PassportModule.register({defaultStrategy: 'jwt'}), CatsModule
],
controllers: [
AppController
],
providers: [
AppService, RoleGuard, JwtStrategy
],
exports: [PassportModule]
}).compile();
}
beforeEach(async () => {
const module = (await createTestModule(
));
app = module.createNestApplication();
server = app.getHttpServer();
await app.init();
});
it('Should create cat for admin', async () => {
return await request(server)
.post('/cats/createcat')
.set('Authorization', catAdminToken)
.set('Accept', 'application/json')
.send(catDto)
.expect(201);
});
Related
i know that this question has already been asked elswhere, but in my case, i followed the jest best practice video from mickael guay (click to view)
But unfortunately i get the jest error, Your test suite must contain at least one test
But one test is passing just after that.
here's my code:
[...]
const mockResponse = {
json: jest.fn(),
status: jest.fn().mockReturnThis(),
} as unknown as Response<any, Record<string, any>>;
describe('UsersController', () => {
let usersController: UsersController;
let usersService: UsersService;
beforeEach(async () => {
const moduleRef = await Test.createTestingModule({
imports: [],
controllers: [UsersController],
providers: [
UsersService,
{ provide: getModelToken(User.name), useValue: jest.fn() },
{ provide: getModelToken(Role.name), useValue: jest.fn() },
],
}).compile();
usersController = moduleRef.get<UsersController>(UsersController);
usersService = moduleRef.get<UsersService>(UsersService);
jest.clearAllMocks();
});
describe('findOneById', () => {
describe('when findOneById is called', () => {
beforeEach(async () => {
const user: Partial<User> & Response =
await usersController.findOneById(mockResponse, userStub().userId);
console.log('user', user);
});
it('then it should call usersService', () => {
expect(usersService.findOneById).toBeCalledWith(userStub().userId);
});
});
});
});
I think maybe it's because i have a describe() nested in a describe(), that makes the first describe() waiting also for a test ?
Thank you very much!
EDIT: figure it out when i realized that i had another old .spec file hidden in another folder. removing it solved the issue.
I'm working on NestJs application and wrote unit test for my authenticateUser function in user.service.ts.It's has pass in my local machine.but when I deployed it in to server and run unit test, i got an error Redis connection to 127.0.0.1:6379 failed - connect ECONNREFUSED.Seems like redis mock is not working.How should I mock redis and resolve this issue for working?
user.service.ts
async authenticateUser(authDto: AuthDTO): Promise<AuthResponse> {
try {
const userData = await this.userRepository.findOne({msisdn});
if(userData){
await this.redisCacheService.setCache(msisdn, userData);
}
} catch (error) {
console.log(error)
}
}
redisCache.service.ts
export class RedisCacheService {
constructor(
#Inject(CACHE_MANAGER) private readonly cache: Cache,
) {}
async setCache(key, value) {
await this.cache.set(key, value);
}
}
user.service.spec.ts
describe('Test User Service', () => {
let userRepository: Repository<UserEntity>;
let userService: UserService;
let redisCacheService: RedisCacheService;
let cacheManager: any;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
UserService,
UserEntity,
RedisCacheService,
{
provide: getRepositoryToken(UserEntity),
useClass: registeredApplicationRepositoryMockFactory,
},
],
imports: [CacheModule.register({})],
}).compile();
userService = module.get<UserService>(UserService);
userRepository = module.get<Repository<UserEntity>>(
getRepositoryToken(UserEntity),
);
redisCacheService = module.get<RedisCacheService>(RedisCacheService);
cacheManager = module.get<any>(CACHE_MANAGER);
});
it('authenticateUser should return success response', async () => {
const userEntity = { id: 1, name: 'abc', age: 25 };
const mockSuccessResponse = new AuthResponse(
HttpStatus.OK,
STRING.SUCCESS,
`${STRING.USER} ${STRING.AUTHENTICATE} ${STRING.SUCCESS}`,
{},
);
jest.spyOn(userRepository, 'findOne').mockResolvedValueOnce(userEntity);
jest.spyOn(redisCacheService, 'setCache').mockResolvedValueOnce(null);
expect(await userService.authenticateUser(mockAuthBody)).toEqual(mockSuccessResponse);
});
});
You can mock CACHE_MANAGER using a custom provider:
import { CACHE_MANAGER } from '#nestjs/common';
import { Cache } from 'cache-manager';
describe('AppService', () => {
let service: AppService;
let cache: Cache;
beforeEach(async () => {
const app = await Test.createTestingModule({
providers: [
AppService,
{
provide: CACHE_MANAGER,
useValue: {
get: () => 'any value',
set: () => jest.fn(),
},
},
],
})
.compile();
service = app.get<AppService>(AppService);
cache = app.get(CACHE_MANAGER);
});
// Then you can use jest.spyOn() to spy and mock
it(`should cache the value`, async () => {
const spy = jest.spyOn(cache, 'set');
await service.cacheSomething();
expect(spy).toHaveBeenCalledTimes(1);
expect(spy.mock.calls[0][0]).toEqual('key');
expect(spy.mock.calls[0][1]).toEqual('value');
});
it(`should get the value from cache`, async () => {
const spy = jest.spyOn(cache, 'get');
await service.getSomething();
expect(spy).toHaveBeenCalledTimes(1);
});
it(`should return the value from the cache`, async () => {
jest.spyOn(cache, 'get').mockResolvedValueOnce('value');
const res = await service.getSomething();
expect(res).toEqual('value');
}),
});
More details on Custom Providers: https://docs.nestjs.com/fundamentals/custom-providers
Two more things, for unit testing you shouldn't import modules but mock the dependencies instead. And as Daniel said, UserService is not using CACHE_MANAGER but RedisCacheService, so you should mock RedisCacheService.
Usually the best thing to do is to only provide the service you're testing and mock the dependencies.
in order to use the jest spy functions you need to return the jest function directly.
providers: [
AppService,
{
provide: CACHE_MANAGER,
useValue: {
get: () => 'any value',
set: jest.fn(),
},
},
],
Imagine I have a Controller defined like so:
class NewsEndpointQueryParameters {
#IsNotEmpty()
q: string;
#IsNotEmpty()
pageNumber: number;
}
#Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
#Get(['', 'ping'])
ping(): PingEndpointResponse {
return this.appService.ping();
}
#Get(['news'])
getNews(
#Query() queryParameters: NewsEndpointQueryParameters
): Observable<NewsEndpointResponse> {
return this.appService.getNews(
queryParameters.q,
queryParameters.pageNumber
);
}
}
I want to be able to test what happens in a request, if, for example, a query parameter is not provided.
Right now this is my testing setup:
describe('AppController', () => {
let app: TestingModule;
let nestApp: INestApplication;
let appService: AppService;
beforeAll(async () => {
app = await Test.createTestingModule({
controllers: [AppController],
providers: [AppService],
imports: [HttpModule],
}).compile();
appService = app.get<AppService>(AppService);
nestApp = app.createNestApplication();
await nestApp.init();
return;
});
describe('/', () => {
test('Return "Pong!"', async () => {
const appServiceSpy = jest.spyOn(appService, 'ping');
appServiceSpy.mockReturnValue({ message: 'Pong!' });
const response = await supertest(nestApp.getHttpServer()).get('/');
expect(response.body).toStrictEqual({
message: 'Pong!',
});
return;
});
});
describe('/ping', () => {
test('Return "Pong!"', async () => {
const appServiceSpy = jest.spyOn(appService, 'ping');
appServiceSpy.mockReturnValue({ message: 'Pong!' });
const response = await supertest(nestApp.getHttpServer()).get('/ping');
expect(response.body).toStrictEqual({
message: 'Pong!',
});
return;
});
});
describe('/news', () => {
describe('Correct query', () => {
beforeEach(() => {
const appServiceSpy = jest.spyOn(appService, 'getNews');
appServiceSpy.mockReturnValue(
new Observable<NewsEndpointResponse>((subscriber) => {
subscriber.next({
data: [{ url: 'test' }],
message: 'test',
status: 200,
});
subscriber.complete();
})
);
return;
});
test('Returns with a custom body response.', async () => {
const response = await supertest(nestApp.getHttpServer()).get(
'/news?q=test&pageNumber=1'
);
expect(response.body).toStrictEqual({
data: [{ url: 'test' }],
message: 'test',
status: 200,
});
return;
});
return;
});
describe('Incorrect query', () => {
test("Returns an error if 'q' query parameter is missing.", async () => {
return;
});
test("Returns an error if 'pageNumber' query parameter is missing.", async () => {
return;
});
return;
});
return;
});
return;
});
If I do nx serve and then curl 'localhost:3333/api/ping', I get:
{"message":"Pong!"}
And if I do curl 'localhost:3333/api/news?q=test&pageNumber=1' I get:
{"data":['lots of interesting news'],"message":"News fetched successfully!","status":200}
Finally, if I do curl 'localhost:3333/api/news?q=test' I get:
{"statusCode":400,"message":["pageNumber should not be empty"],"error":"Bad Request"}
How can I replicate the last case? If I use supertest, there is no error returned like the above. I haven't found a way to mock the Controller's function too.
A very special thank to #jmcdo29 for explaining me how to do this.
Code:
beforeAll(async () => {
app = await Test.createTestingModule({
controllers: [AppController],
providers: [
AppService,
{ provide: APP_PIPE, useValue: new ValidationPipe() },
],
imports: [HttpModule, AppModule],
}).compile();
appService = app.get<AppService>(AppService);
nestApp = app.createNestApplication();
await nestApp.init();
return;
});
Explanation:
We need to model the behavior of bootstrap() in main.ts. In my case, in looks like this:
async function bootstrap() {
const app = await NestFactory.create(AppModule, {
cors: environment.nestCors,
});
app.useGlobalPipes(new ValidationPipe());
const globalPrefix = 'api';
app.setGlobalPrefix(globalPrefix);
const port = process.env.PORT || 3333;
await app.listen(port, () => {
Logger.log('Listening at http://localhost:' + port + '/' + globalPrefix);
});
}
Instead of importing the AppModule, we could also configure the app created for testing like so: nestApp.useGlobalPipes(new ValidationPipe()) (this needs to be done before await nestApp.init())
I'm testing a nest.js service, this method basically will receive a find method from sequelize:
...
import { Auth } from './../../modules/auth/entities/auth.entity';
import { Sequelize } from 'sequelize-typescript';
import { getModelToken } from '#nestjs/sequelize';
jest.mock('./../../modules/auth/auth.service');
const sequelize = new Sequelize({ validateOnly: true });
sequelize.addModels([Auth]);
const testAuth = new Auth({
id_user: '1',
});
describe('AuthGuardService', () => {
let service: AuthGuardService;
let authService: AuthService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
AuthGuardService,
AuthService,
{
provide: getModelToken(Auth),
useValue: { findOne: jest.fn(() => testAuth) },
},
],
imports: [HttpModule],
}).compile();
service = module.get<AuthGuardService>(AuthGuardService);
authService = module.get<AuthService>(AuthService);
});
...
it('it should return true', async () => {
expect(
await authService.find({
id: '1',
})
).toEqual(testAuth);
});
on my service I'm trying to test this exactly block:
const user = await this.authService.find({
id: value,
});
if(user.id === some_value) {
return true;
}
but I always get undefined when I tried to read user from my test.
TypeError: Cannot read property 'id' of undefined
I'm trying to do a e2e testing to a route that has an AuthGuard from nestjs passport module and I don't really know how to approach it. When I run the tests it says:
[ExceptionHandler] Unknown authentication strategy "bearer"
I haven't mock it yet so I suppose it's because of that but I don't know how to do it.
This is what I have so far:
player.e2e-spec.ts
import { Test } from '#nestjs/testing';
import { INestApplication } from '#nestjs/common';
import * as request from 'supertest';
import { PlayerModule } from '../src/modules/player.module';
import { PlayerService } from '../src/services/player.service';
import { Repository } from 'typeorm';
describe('/player', () => {
let app: INestApplication;
const playerService = { updatePasswordById: (id, password) => undefined };
beforeAll(async () => {
const module = await Test.createTestingModule({
imports: [PlayerModule],
})
.overrideProvider(PlayerService)
.useValue(playerService)
.overrideProvider('PlayerRepository')
.useClass(Repository)
.compile();
app = module.createNestApplication();
await app.init();
});
it('PATCH /password', () => {
return request(app.getHttpServer())
.patch('/player/password')
.expect(200);
});
});
player.module.ts
import { Module } from '#nestjs/common';
import { PlayerService } from 'services/player.service';
import { PlayerController } from 'controllers/player.controller';
import { TypeOrmModule } from '#nestjs/typeorm';
import { Player } from 'entities/player.entity';
import { PassportModule } from '#nestjs/passport';
#Module({
imports: [
TypeOrmModule.forFeature([Player]),
PassportModule.register({ defaultStrategy: 'bearer' }),
],
providers: [PlayerService],
controllers: [PlayerController],
exports: [PlayerService],
})
export class PlayerModule {}
Below is a e2e test for an auth API based using TypeORM and the passportjs module for NestJs. the auth/authorize API checks to see if the user is logged in. The auth/login API validates a username/password combination and returns a JSON Web Token (JWT) if the lookup is successful.
import { HttpStatus, INestApplication } from '#nestjs/common';
import { Test } from '#nestjs/testing';
import { TypeOrmModule } from '#nestjs/typeorm';
import * as request from 'supertest';
import { UserAuthInfo } from '../src/user/user.auth.info';
import { UserModule } from '../src/user/user.module';
import { AuthModule } from './../src/auth/auth.module';
import { JWT } from './../src/auth/jwt.type';
import { User } from '../src/entity/user';
describe('AuthController (e2e)', () => {
let app: INestApplication;
let authToken: JWT;
beforeAll(async () => {
const moduleFixture = await Test.createTestingModule({
imports: [TypeOrmModule.forRoot(), AuthModule],
}).compile();
app = moduleFixture.createNestApplication();
await app.init();
});
it('should detect that we are not logged in', () => {
return request(app.getHttpServer())
.get('/auth/authorized')
.expect(HttpStatus.UNAUTHORIZED);
});
it('disallow invalid credentials', async () => {
const authInfo: UserAuthInfo = {username: 'auser', password: 'badpass'};
const response = await request(app.getHttpServer())
.post('/auth/login')
.send(authInfo);
expect(response.status).toBe(HttpStatus.UNAUTHORIZED);
});
it('return an authorization token for valid credentials', async () => {
const authInfo: UserAuthInfo = {username: 'auser', password: 'goodpass'};
const response = await request(app.getHttpServer())
.post('/auth/login')
.send(authInfo);
expect(response.status).toBe(HttpStatus.OK);
expect(response.body.user.username).toBe('auser');
expect(response.body.user.firstName).toBe('Adam');
expect(response.body.user.lastName).toBe('User');
authToken = response.body.token;
});
it('should show that we are logged in', () => {
return request(app.getHttpServer())
.get('/auth/authorized')
.set('Authorization', `Bearer ${authToken}`)
.expect(HttpStatus.OK);
});
});
Note since this is an end-to-end test, it doesn't use mocking (at least my end-to-end tests don't :)). Hope this is helpful.
You can just generate a valid token and send it inside header for authentication using passport (jwt,...):
beforeAll(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [AppModule],
})
.compile();
productService = moduleFixture.get<ProductService>(ProductService);
userService = moduleFixture.get<UsersService>(UsersService);
authService = moduleFixture.get<AuthenticationService>(
AuthenticationService,
);
app = moduleFixture.createNestApplication();
await app.init();
});
afterAll(async () => {
await app.close();
});
beforeEach(async () => {
const signupResult = await authService.signup({
email: 'test#test.com',
name: 'test user',
password: 'testpassword',
});
accessToken = signupResult.token.access_token; // this line find a valid access token and you can pass this to your tests
});
Pass access_token to requests:
it('should return 201 and return product', async () => {
return request(app.getHttpServer())
.post('/products')
.set('Authorization', `Bearer ${accessToken}`) // this is for Authentication
.send({})
.expect(201)
.expect(({ body }) => {
expect(body.id).toBeDefined();
});
});