jest - how to test a function within a method - node.js

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');
})

Related

Testing firestore with jest not working as expected

I have the following service which I just need to test that whenever an user is not validated, we throw an error
async payoutPendingRewards(userId: string): Promise<Reward[]> {
const db = admin.firestore();
const userRef = db.collection('UserProfiles').doc(userId);
const doc = await userRef.get();
if (!doc.data() || !doc.data().validated) {
throw Error('User profile has not been validated');
}
const userBankAccount = await this.getUserPayoutAccount(userId);
if (userBankAccount) {
const pendingRewards = await this.getPendingRewards(userId);
const amount = pendingRewards.reduce(
(amount, reward) => amount + reward.amount,
0,
);
if (amount > 0) {
try {
const payoutId = await this.createPayout(
userBankAccount.accountType,
userBankAccount.accountId,
amount,
);
if (payoutId) {
// TODO: add payout ID to Reward
return await this.updatePendingRewards(pendingRewards);
}
} catch (e) {
throw new HttpException(
{
status: HttpStatus.INTERNAL_SERVER_ERROR,
message: 'There was an error: Payout Not Created',
},
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
}
When trying to test it using jest, I can't get to mock the firebase-admin functions. I get a firebase error when trying to do it
import { Test, TestingModule } from '#nestjs/testing';
import { HttpModule, HttpService } from '#nestjs/axios';
import { describe, expect, beforeEach, it, jest } from '#jest/globals';
import { INestApplication, forwardRef } from '#nestjs/common';
import { SecretManagerServiceClient } from '#google-cloud/secret-manager';
import * as request from 'supertest';
import { RewardsService } from '../src/rewards/rewards.service';
import { RewardsController } from '../src/rewards/rewards.controller';
import { UsersModule } from '../src/users/users.module';
import { FirebaseAuthGuard } from '../src/auth/firebase-auth.guard';
import { HttpExceptionFilter } from '../src/exceptions/http-exception.filter';
import { TransformInterceptor } from '../src/interceptors/transform.interceptor';
import { firestore } from './setupFirestoreTests';
describe('RewardsService', () => {
let service: RewardsService;
let httpService: HttpService;
let app: INestApplication;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [RewardsService],
controllers: [RewardsController],
imports: [
FirebaseAuthGuard,
HttpExceptionFilter,
TransformInterceptor,
HttpModule,
forwardRef(() => UsersModule),
],
}).compile();
app = module.createNestApplication();
service = module.get<RewardsService>(RewardsService);
httpService = module.get<HttpService>(HttpService);
await app.init();
});
afterEach(() => {
jest.restoreAllMocks();
});
it('should be defined', () => {
expect(service).toBeDefined();
});
it('makes a call to firestore', async () => {
service.payoutPendingRewards('123');
expect(firestore().doc().get).toHaveBeenCalledWith('123');
});
});
I need help just mocking the firebase functions. Already tried by creating mock functions on a different file but it didn't work

E2E Testing NestJS

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.

Nuxt Vitest Mock useRoute in another module

I am trying to write a test for the following Nuxt 3 composable (useLinkToSlug).
import { computed } from 'vue';
export default function () {
const route = useRoute();
return computed(() => route?.params?.slug ? `/${route.params.slug}` : undefined);
}
To keep the code as lean as possible, I tried to mock the vue-router module and set the return of useRoute() manually.
My test looks like this:
import { vi, it, expect, describe } from 'vitest';
import useLinkToSlug from '~~/composables/useLinkToSlug';
describe('useLinkToSlug', () => {
it('should return link to slug', () => {
vi.mock('vue-router', () => ({
useRoute: () => ({ params: { slug: 'abc' } })
}));
const link = useLinkToSlug();
expect(link.value).toEqual('/abc');
});
it('should return null', () => {
vi.mock('vue-router', () => ({
useRoute: () => ({ params: { slug: undefined } })
}));
const link = useLinkToSlug();
expect(link.value).toBeNull();
});
});
The first one succeeds, but the later one fails, with:
AssertionError: expected '/abc' to be null
I don't get why and what to do, to make this work.
Using: Nuxt3 with Vitest
Got it now. Was so close. Solution:
Add import statement for useRoute to composable.
import { computed } from 'vue';
import { useRoute } from 'vue-router'; // added this import statement
export default function () {
const route = useRoute();
return computed(() => route?.params?.slug ? `/${route.params.slug}` : undefined);
}
Mock vue-router:
import { vi, it, expect, describe } from 'vitest';
import useLinkToSlug from '~~/composables/useLinkToSlug';
vi.mock('vue-router'); // mock the import
describe('useLinkToSlug', () => {
it('should return link to slug', () => {
const VueRouter = await import('vue-router');
VueRouter.useRoute.mockReturnValueOnce({
params: { slug: 'abc' }
});
const link = useLinkToSlug();
expect(link.value).toEqual('/abc');
});
it('should return null', () => {
const VueRouter = await import('vue-router');
VueRouter.useRoute.mockReturnValueOnce({
params: { slug: undefined }
});
const link = useLinkToSlug();
expect(link.value).toBeNull();
});
});

NestJs Testing create a single instance of app across all tests

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()
}

Testing Passport in NestJS

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();
});
});

Resources