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.
Related
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 have a nestjs service that uses mongoose. In a function I wish to test it creates a new model, but I could not find a way to mock that.
I have the following service
#Injectable()
export class ProjectService {
constructor(
#InjectModel("Project") private projectModel: Model<ProjectDocument>,
) {}
public dto2ProjectModel(dto: ProjectDto) {
return new this.projectModel({
_id: dto.id? Types.ObjectId(dto.id) : Types.ObjectId(),
name: dto.name.toUpperCase()
});
}
}
And I created the test following the documentation like so:
describe('ProjectService tests', () => {
let projectService: ProjectService;
beforeEach(async () => {
const moduleRef = await Test.createTestingModule({
providers: [
ProjectService,
{
provide: getModelToken("Project"),
useValue: {
find: jest.fn(),
findById: jest.fn()
}
}
]
}).compile();
projectService = moduleRef.get<ProjectService>(ProjectService);
});
describe('dto2ProjectModel', () => {
it('should return a project with the uppercase name', async () => {
const projectDto: ProjectDto = new ProjectDto();
projectDto.id = '5a2539b41c574006c46f1a09';
projectDto.name = 'someName';
expect(await projectService.dto2ProjectModel(projectDto).name).toEqual('SOMENAME');
});
});
});
I tried doing it by example on nestjs documentation and mocking model methods like find is fine, but the new this.projectModel({}) does not work.
Any ideas are greatly appreciated!
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);
});
I am getting issues while unit testing my controller and getting an error "Nest can't resolve dependencies of my service".
For maximum coverage I wanted to unit test controller and respective services and would like to mock external dependencies like mongoose connection. For the same I already tried suggestions mentioned in the below link but didn't find any luck with that:
https://github.com/nestjs/nest/issues/194#issuecomment-342219043
Please find my code below:
export const deviceProviders = [
{
provide: 'devices',
useFactory: (connection: Connection) => connection.model('devices', DeviceSchema),
inject: ['DbConnectionToken'],
},
];
export class DeviceService extends BaseService {
constructor(#InjectModel('devices') private readonly _deviceModel: Model<Device>) {
super();
}
async getDevices(group): Promise<any> {
try {
return await this._deviceModel.find({ Group: group }).exec();
} catch (error) {
return Promise.reject(error);
}
}
}
#Controller()
export class DeviceController {
constructor(private readonly deviceService: DeviceService) {
}
#Get(':group')
async getDevices(#Res() response, #Param('group') group): Promise<any> {
try {
const result = await this.deviceService.getDevices(group);
return response.send(result);
}
catch (err) {
return response.status(422).send(err);
}
}
}
#Module({
imports: [MongooseModule.forFeature([{ name: 'devices', schema: DeviceSchema }])],
controllers: [DeviceController],
components: [DeviceService, ...deviceProviders],
})
export class DeviceModule { }
Unit test:
describe('DeviceController', () => {
let deviceController: DeviceController;
let deviceService: DeviceService;
const response = {
send: (body?: any) => { },
status: (code: number) => response,
};
beforeEach(async () => {
const module = await Test.createTestingModule({
controllers: [DeviceController],
components: [DeviceService, ...deviceProviders],
}).compile();
deviceService = module.get<DeviceService>(DeviceService);
deviceController = module.get<DeviceController>(DeviceController);
});
describe('getDevices()', () => {
it('should return an array of devices', async () => {
const result = [{
Group: 'group_abc',
DeviceId: 'device_abc',
},
{
Group: 'group_xyz',
DeviceId: 'device_xyz',
}];
jest.spyOn(deviceService, 'getDevices').mockImplementation(() => result);
expect(await deviceController.getDevices(response, null)).toBe(result);
});
});
});
When I am running my test case above, I am getting two errors:
Nest can't resolve dependencies of the DeviceService (?). Please make sure that the argument at index [0] is available in the current context.
Cannot spyOn on a primitive value; undefined given
Example code:
import { Test } from '#nestjs/testing';
import { getModelToken } from '#nestjs/mongoose';
describe('auth', () => {
let deviceController: DeviceController;
let deviceService: DeviceService;
const mockRepository = {
find() {
return {};
}
};
beforeAll(async () => {
const module = await Test.createTestingModule({
imports: [DeviceModule]
})
.overrideProvider(getModelToken('Auth'))
.useValue(mockRepository)
.compile();
deviceService = module.get<DeviceService>(DeviceService);
});
// ...
});
You are not injecting the correct token here. Instead of a plain string you have to use the function getModelToken.
import { getModelToken } from '#nestjs/mongoose';
// ...
{ provide: getModelToken('devices'), useFactory: myFactory },
Here is the solution provided by this repo. See the mongo-sample. I am testing my API using the #injectModel and another service. Here's the snippet:
import { CategoriesService } from './../categories/categories.service';
import { getModelToken } from '#nestjs/mongoose';
import { Test, TestingModule } from '#nestjs/testing';
import { ProductsService } from './products.service';
describe('ProductsService', () => {
let service: ProductsService;
beforeAll(async () => {
const module: TestingModule = await Test.createTestingModule({
// getModelToken to mock the MongoDB connection
providers: [
ProductsService,
CategoriesService,
{
provide: getModelToken('Product'),
useValue: {
find: jest.fn(),
findOne: jest.fn(),
findByIdAndUpdate: jest.fn(),
findByIdAndRemove: jest.fn(),
save: jest.fn(),
},
},
{
provide: getModelToken('Category'),
useValue: {
find: jest.fn(),
findOne: jest.fn(),
findByIdAndUpdate: jest.fn(),
findByIdAndRemove: jest.fn(),
save: jest.fn(),
},
},
],
}).compile();
service = module.get<ProductsService>(ProductsService);
});
// your test case
});