How can I test the function called inside another function in Nestjs? - javascript

i am writing test functions in nestjs and i am new to this task.
I mostly had problems with typeorm. but my current problem is that I do not call the paginate function in the PaginateLib class inside the function I wrote in the service. I tested the userList function directly before, but it gave paginate undefined error. Now I used mock and paginate function in test. it still gives undefined error.
Here's my admin.service code
public async userList(page, limit): Promise<any> {
const opt = {
relations: ['userInfo'],
};
const userData = await this.paginateLib.paginate(
getRepository(Users),
opt,
page,
limit,
);
return userData;
}
Here's Paginate function code in PaginateLib class. I'm cutting this short as I don't think it's very necessary.
export class PaginateLib {
async paginate(repo, opt, page, limit) {
try {
page = Number(page);
limit = Number(limit);
const items = (Number(page) - 1) * Number(limit);
const [data, count] = await repo.findAndCount({
...opt,
skip: Number(items),
take: Number(limit),
});
if (count <= items) {
return [];
}
and here's my test code
class PaginateMock {
paginate(repo: any, opt: any, page: number, limit: number) {
return [];
}
}
describe('AdminService', () => {
let service: AdminService;
let connection: Connection;
let module: TestingModule;
let services: PaginateLib;
beforeAll(async () => {
const ApiServiceProvider = {
provide: PaginateLib,
useClass: PaginateMock,
},
module = await Test.createTestingModule({
imports: [
TypeOrmModule.forRoot({
type: '****',
host: '****',
port: ****,
username: '****',
password: '****',
database: '****',
entities: [__dirname + '/../../**/*.entity.ts'],
synchronize: true,
}),
],
providers: [
AdminService,
ApiServiceProvider,
],
}).compile();
service = module.get<AdminService>(AdminService);
services = module.get<PaginateLib>(PaginateLib);
});
// afterAll(async () => {
// await module.close();
// });
describe('User Info', () => {
it('should be get user list', async () => {
const paginateSpy = jest.spyOn(services, 'paginate');
expect(paginateSpy).toHaveBeenCalled();
expect(true).toBe(service.userList(1, 1));
});
});
});
I tried different things many times. but the error I get is undefined. what should I do? Is there anyone have an idea?thank you

Try this:
describe('User Info', () => {
it('should be get user list', async () => {
const paginateSpy = jest.spyOn(services, 'paginate');
const userData = await service.userList(1, 1);
expect(paginateSpy).toHaveBeenCalled();
expect(userData).toBe(); // Don't know what userData actually looks like. So this is up to you
});
});

Related

Proxyquire not calling inner functions (npm modules) and does not work with classes properly

Where am i going wrong here?
Using mocha, chai, sinon and proxyquire for an express server and sequelize ORM linked with a postgres database
I am trying to test a login controller route from my express server
Before I show the file which I want to run my test on here is what "../services/authService.js" file looks like
../services/authService
const UserService = require("./userService");
module.exports = class AuthService extends UserService {
};
// so UserService will have the method findByEmail
// UserService class looks like this and it is coming from another file require("./userService.js) as stated above
/*
class UserService {
async findByEmail(email) {
try {
const user = await User.findOne({ where: { email: email }});
if (user) {
return user;
}
throw new Error("User not found");
} catch (err) {
err.code = 404;
throw err
}
}
}
*/
And here is the auth-controller.js file which I want to run the test on
auth-controller.js
const bcrypt = require('bcryptjs');
const AuthService = require("../services/authService"); // is a class which extends from another calls see the code above
const authService = new AuthService();
const jwtGenerator = require('../utils/jwtGenerator');
const createError = require("http-errors");
exports.loginRoute = async (req, res, next) => {
try {
req.body.password = String(req.body.password);
// db query trying to force a sinon.stub to resolve a fake value. But code wont pass here hence 500 error
const userQuery = await authService.findByEmail(req.body.email);
const compare = await bcrypt.compare(req.body.password, userQuery.password);
if (!compare) throw createError(401, 'Incorrect password.');
const user = {
id: userQuery.id, role: userQuery.is_admin ? "Administrator" : "User", email: userQuery.email, Authorized: true
}
const token = jwtGenerator(user);
return res
.cookie("access_token", token, {
httpOnly: true,
secure: process.env.NODE_ENV === "production",
}).status(200).json({ message: "Logged in successfully 😊 πŸ‘Œ", user, token });
} catch (error) {
next(error);
}
}
This code works in production but I cannot seem to test it. I used proxyquire to require the modules that the function uses. I have a big problem in making proxyquire work when it comes to my class AuthService here is my test file. As proxyquire is not working with classes some how. proxyquire is not using make AuthServiceMock at all cant figure out why.
First of these are my helper variables which I will use in the test file
../test-utils/user-helper
const createAccessToken = (payload) => jwt.sign(payload, TOKEN, {expiresIn: "1h"});
let loginDetail = {
email: "admin#test.com",
password: "123456"
};
let loginAdminUser = {
id: 1,
email: "admin#test.com",
password: "123456",
is_admin: true
}
const loginUser = {
id: 1,
email: "admin#test.com",
password: "123456",
is_admin: true
}
const adminUser = {
id: 1,
email: 'admin#test.com',
password: '123456',
is_admin: true,
first_name: 'john',
last_name: 'doe',
created_at: "2020-06-26T09:31:36.630Z",
updated_at: "2020-06-26T09:31:49.627Z"
}
module.exports = {
createAccessToken,
loginDetail,
loginAdminUser,
loginUser,
adminUser
}
And here is the test file I placed comments espcially around proxyquire when I am trying to use it as this is giving me some issues when it comes to using it with classes. And as well it is not calling mocked/stubbed npm modules for some reason
auth-controller.spec.js
"use strict";
const _ = require("lodash");
const path = require("path");
const proxyquire = require("proxyquire").noCallThru().noPreserveCache();
const chai = require("chai");
const { expect } = chai;
const sinon = require("sinon");
const sinonChai = require("sinon-chai");
chai.use(sinonChai);
// const AuthServiceOriginalClass = require("../../services/authService"); If i use this directly in proxyquire it calls the original class
const { createAccessToken, loginDetail, loginAdminUser, loginUser, adminUser } = require("../test-utils/user-helper");
const controllerPath = path.resolve('./controllers/authController.js');
describe("login route", () => {
let proxy, authService, bcryptStub, fakeCallback, fakeReq, fakeRes, fakeNext, resolveFn, token;
let result, bcryptStubbing, response;
class UserServiceMock {
async findByEmail(email) {
try {
if (email) {
return loginAdminUser;
}
} catch (error) {
throw error;
}
}
}
class AuthServiceMock extends UserServiceMock {};
bcryptStub = {
compare: function() { return true }
};
let tokeen = (kk) => {
return createAccessToken(kk);
}
// token = sinon.mock(createAccessToken(loginAdminUser)); // ?? which 1 to use?
token = sinon.spy(createAccessToken); // ?? which 1 to use?
// token = sinon.stub(createAccessToken) ?? which 1 to use?
proxy = proxyquire(controllerPath, {
"../services/authService.js": AuthServiceMock, // seems like this is not called at all
// "../services/authService.js": AuthServiceOriginalClass, // commented out if use this instead it calls the original class instant
"bcryptjs": bcryptStub,
"../utils/jwtGenerator": token,
// "#noCallThru": true // keep on or off?
});
before("Stub my methods", () => {
authService = new AuthServiceMock();
// If I call the entire loginRoute I want this stub authTry to be called inside of it and resolve that object value
authTry = sinon.stub(authService, "findByEmail").withArgs(loginDetail.email).resolves(loginAdminUser);
sinon.stub(bcryptStub, "compare").resolves(true); // force it to return true as that seems to be like the code of authController.js
// sinon.stub(token, "createAccessToken")
});
before("call the function loginRoute", async () => {
// fakeCallback = new Promise((res, rej) => {
// resolveFn = res
// });
fakeReq = {
body: {
email: loginDetail.email,
password: loginDetail.password
}
};
fakeRes = {
cookie: sinon.spy(),
status: sinon.spy(),
json: sinon.spy()
}
fakeNext = sinon.stub();
await proxy.loginRoute(fakeReq, fakeReq, fakeNext).then((_result) => {
result = _result;
});
console.log("result")
console.log(result) // undefined
console.log("result")
});
it("login route test if the stubs are called", async () => {
expect(authService.findByEmail).to.have.been.called // never called
// expect(bcryptStubbing).to.have.been.called // never called
// expect(response.status).to.deep.equal(200); // doesn't work
}).timeout(10000);
after(() => {
sinon.reset()
});
});
Where am i going wrong here in the test?

Mock stripe apis using jest in node

I want to mock stripe apis but unable to figure out how to do it. I'm creating the session using below code-
const stripe = require('stripe')('key');
const session = await stripe.checkout.sessions.create({
payment_method_types: ['card'],
line_items: [
{
price_data: {
currency: 'usd',
product_data: {
name: 'abs',
images: ['url'],
},
unit_amount: 100,
},
quantity: 1,
},
],
mode: 'payment',
success_url: `${YOUR_DOMAIN}?success=true&session_id={CHECKOUT_SESSION_ID}`,
cancel_url: `${YOUR_DOMAIN}?back=true&order_id=${req.clientReferenceId}`,
metadata: req.metadata,
client_reference_id: req.clientReferenceId,
customer_email: req.customerEmail,
billing_address_collection: 'required',
});
I cannot import stripe in test file as it will require API key. How to do this?
PS- I tried by following this - Jest to mock Stripe but it is not working for me.
stripe exported as a function, when you want to mock it you can use jest.doMock helper.
Imagine that you have an index.js like this:
const stripe = require('stripe')('key'); // You use require('stripe') as a function
exports.CreateSession = async (req) => {
const session = await stripe.checkout.sessions.create({
// information to create session
});
return session;
}
Then, testing file will be like this:
describe('Create session', () => {
beforeEach(() => {
jest.resetModules(); // importance line
});
it('creates the session', async () => {
const req = {
unitAmount: 100,
imageUrl: '',
metadata: {
userId: '1'
}
};
jest.doMock('stripe', () => {
// instead of mocking return an object, let’s return a function
return jest.fn(() => ({ // when the function be called, it return an object like this
checkout: {
sessions: {
create: jest.fn(() => Promise.resolve({
sessionId: '123' // sessionId instead of id , right?
})),
},
},
}));
});
const { CreateSession } = require('./index'); // import function what you need to test
const resp = await CreateSession(req); // execute with a parameter
expect(resp.sessionId).toBe('123');
});
});
You need to create the stripe mock before use it:
jest.mock("stripe", ()=> ({
checkout: {
sessions: {
create: jest.fn(()=> Promise.resolve()) //--> resolve data you are expecting
}
}
}));
Also you can use the Manual Mocks
__mocks__/stripe.js
const stripe = {
checkout: {
sessions: {
create: jest.fn(()=> Promise.resolve()) //--> resolve data you are expecting
}
}
};
export default stripe;

Using test database when e2e-testing NestJS

In this project, it uses NestJS along with TypeORM. For real API requests, CRUD operation is being operated on MySQL(which is using AWS RDS).
Now I am trying to use SQLite(In-Memory) to test API results.
I successfully implemented this in Unit Test, as the code below.
First, below is create-memory-db.ts, which returns a connection to in-memory SQLite database.
type Entity = Function | string | EntitySchema<any>;
export async function createMemoryDB(entities: Entity[]) {
return createConnection({
type: 'sqlite',
database: ':memory:',
entities,
logging: false,
synchronize: true,
});
}
And by using the exported function above, I successfully ran Unit test, like below.
describe('UserService Logic Test', () => {
let userService: UserService;
let connection: Connection;
let userRepository: Repository<User>;
beforeAll(async () => {
connection = await createMemoryDB([User]);
userRepository = await connection.getRepository(User);
userService = new UserService(userRepository);
});
afterAll(async () => {
await connection.close();
});
afterEach(async () => {
await userRepository.query('DELETE FROM users');
});
// testing codes.
});
I am trying to do the same thing on e2e tests. I tried below code.
// user.e2e-spec.ts
describe('UserController (e2e)', () => {
let userController: UserController;
let userService: UserService;
let userRepository: Repository<User>;
let connection: Connection;
let app: INestApplication;
const NAME = 'NAME';
const EMAIL = 'test#test.com';
const PASSWORD = '12345asbcd';
beforeAll(async () => {
connection = await createMemoryDB([User]);
userRepository = await connection.getRepository(User);
userService = new UserService(userRepository);
userController = new UserController(userService);
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [],
controllers: [UserController],
providers: [UserService],
}).compile();
app = moduleFixture.createNestApplication();
await app.init();
});
afterAll(async () => {
await connection.close();
});
afterEach(async () => {
// await userRepository.query('DELETE FROM users');
});
it('[POST] /user : Response is OK if conditions are right', () => {
const dto = new UserCreateDto();
dto.name = NAME;
dto.email = EMAIL;
dto.password = PASSWORD;
return request(app.getHttpServer())
.post('/user')
.send(JSON.stringify(dto))
.expect(HttpStatus.CREATED);
});
});
I cannot create UserModule since it doesn't have a constructor with Connection parameter.
The code itself has no compile error, but gets results below when e2e test is executed.
Nest can't resolve dependencies of the UserService (?). Please make sure that the argument UserRepository at index[0] is available in the RootTestModule context.
Potential solutions:
- If UserRepository is a provider, is it part of the current RootTestModule?
- If UserRepository is exported from a seperate #Module, is that module imported within RootTestModule?
#Module({
imports: [/* The module containing UserRepository */]
})
TypeError: Cannot read property 'getHttpServer' of undefined.
Any help would be greatly appreciated. Thanks :)
UPDATE : New error occured after trying below.
describe('UserController (e2e)', () => {
let userService: UserService;
let userRepository: Repository<User>;
let connection: Connection;
let app: INestApplication;
const NAME = 'NAME';
const EMAIL = 'test#test.com';
const PASSWORD = '12345asbcd';
beforeAll(async () => {
connection = await createMemoryDB([User]);
userRepository = await connection.getRepository(User);
userService = new UserService(userRepository);
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [UserModule],
})
.overrideProvider(UserService)
.useClass(userService)
.compile();
app = moduleFixture.createNestApplication();
await app.init();
});
afterAll(async () => {
await connection.close();
});
afterEach(async () => {
await userRepository.query('DELETE FROM users');
});
it('[POST] /user : Response is OK if conditions are right', async () => {
const dto = new UserCreateDto();
dto.name = NAME;
dto.email = EMAIL;
dto.password = PASSWORD;
const result = await request(app.getHttpServer())
.post('/user')
.send(JSON.stringify(dto))
.expect({ status: HttpStatus.CREATED });
});
});
I checked if query is working, and was able to see that it is using SQLite database as I wanted. But new error appeared in console.
TypeError: metatype is not a constructor.
TypeError: Cannot read property 'getHttpServer' of undefined.
Okay, I solved this issue by using TypeOrm.forRoot() inside the imports field of Test.createTestingModule. Below is how I did it.
describe('UserController (e2e)', () => {
let userService: UserService;
let userRepository: Repository<User>;
let app: INestApplication;
const NAME = 'NAME';
const EMAIL = 'test#test.com';
const PASSWORD = '12345asbcd';
beforeAll(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [
UserModule,
TypeOrmModule.forRoot({
type: 'sqlite',
database: ':memory:',
entities: [User],
logging: true,
synchronize: true,
}),
],
}).compile();
app = moduleFixture.createNestApplication();
await app.init();
userRepository = moduleFixture.get('UserRepository');
userService = new UserService(userRepository);
});
afterAll(async () => {
await app.close();
});
afterEach(async () => {
await userRepository.query('DELETE FROM users');
});
});
For those looking for setup e2e tests that hit endpoints and assert the response body, you can do something like this:
// app.module.ts
#Module({
imports: [
TypeOrmModule.forRootAsync({
useFactory: async (configService: ConfigService) => {
if (process.env.APPLICATION_ENV === 'test') {
return {
type: 'sqlite',
database: ':memory:',
entities: [Entity],
synchronize: true,
}
}
return {
// your default options
};
},
}),
]
})

how can I reimplement a Mock function of a manually mocked module?

I am working in a test module for my application which uses Sequelize to make queries to a database. I am trying to mock this module and I found this answer which was oriented to typescript: How to mock Sequelize with Jest?
I am doing my test in javascript and I find the mocked function isn't working. So I would like to find an alternative to call the function member of a mocked module inside a test block and perform an implementation of it. I'm not sure if it is a good practice, but I didn't find another way to test the procedure, so I require to reimplement the member definition, however when I run the test, it appears as the reimplementation wasn't performed.
The function I was intended to test is in file sql-api.js
const initDB = async function () {
await connect()
await synchronizePatients()
};
const sequelize = new Sequelize(config.db.database, config.db.username, config.db.password, {
host: config.db.host,
dialect: 'postgres'
});
const connect = async function () {
try {
return await sequelize.authenticate();
} catch (error) {
console.error('Unable to connect to the database:', error);
return error;
}
}
const synchronizePatients = async function () {
console.log("Synchronize patients")
return await Patients.sync({ alter: true })
}
const Patients = sequelize.define('patients', {
uuid: {
primaryKey: true,
allowNull: false,
type: DataTypes.UUID,
defaultValue: Sequelize.UUIDV4 // Or Sequelize.UUIDV1
},
id: {
type: DataTypes.INTEGER,
},
nombres: {
type: DataTypes.STRING,
allowNull: false
},
apellidos: {
type: DataTypes.STRING,
allowNull: false
}, {
// Other model options go here
timestamps: true,
});
Then I have a mock module in __mock__ folder, named sequelized.js
'use strict';
const sequelize = jest.createMockFromModule('sequelize');
const mSequelize = {
authenticate: jest.fn(),
define: jest.fn()
};
const actualSequelize = jest.requireActual('sequelize');
sequelize.Sequelize = jest.fn(() => mSequelize);
sequelize.DataTypes = actualSequelize.DataTypes;
module.exports= sequelize;
Then in my test code in __tests__ folder:
const sqlapi = require("../sql-api");
const { Sequelize, Op } = require('sequelize');
const config = require("../config.json")
const mPatients = { sync: jest.fn() };
const mUsers = {
sync: jest.fn(),
findOne: jest.fn(),
create: jest.fn()
};
jest.mock("sequelize");
const mSequelizeContext = new Sequelize();
describe("Testing setup DB", () => {
afterAll(() => {
jest.resetAllMocks();
});
test("It should connect correctly", async () => {
mSequelizeContext.define.mockImplementation((modelName) => {
switch (modelName) {
case 'patients':
return mPatients;
case 'users':
return mUsers;
}
})
await sqlapi.initDB();
expect(Sequelize).toBeCalledWith(config.db.database, config.db.username, config.db.password, {
host: config.db.host,
dialect: 'postgres'
});
expect(mSequelizeContext.authenticate).toBeCalled();
})
})
However, when I try to run the code, I have the following error:
TypeError: Cannot read property 'sync' of undefined
374 | const synchronizePatients = async function () {
375 | console.log("Synchronize patients")
> 376 | return await Patients.sync({ alter: true })
| ^
377 | }
As the function define wasn't reimplemented in mSequelizeContext.define.mockImplementation(...). I don't know why it doesn't work. In the example I referred first, to call the mocked function they use a mocked(mSequelizeContext.define), however, that mocked function is part of another library ts-jest/utils oriented to typescript. I was wondering that in Javascript it isn't needed.
I also noticed that replacing the implementation in the mock folder by:
const mSequelize = {
authenticate: jest.fn(),
define: jest.fn().mockImplementation((modelName) => {
switch (modelName) {
case 'patients':
return mPatients;
case 'users':
return mUsers;
}
})
};
And here it works, but by the other way, reimplementing inside the test procedure, it doesn't.
Please, if you could orient me in this implementation.

How can I mock a fake database for when unit testing against Knex?

I've been using Knex successfully to connect to a backend database. But I want to be able to unit test my code. Is there a way to mock the database connection?
I've tried using proxyquire but I can't seem to get it to work.
The problem seems to be with the way Knex is initialized.
var knex = require('knex')({
client: 'mysql',
connection: {}
});
I setup knex to be mocked in my unit test.
myService = proxyquire('../app/myService', {
'knex': knexProxy
});
My service includes knex.
var knex = require('knex').knex,
When my service runs a query, it fails.
var sql = knex("table_name");
sql.insert(rowToInsert, "auto_increment_id");
sql.then(function (insertId) {
resolve();
}, function (err) {
reject(err);
});
For some reason I just can't seem to capture the request before it attempts the connection.
I've also, tried to create a custom Knex Client, but that hasn't worked yet either.
Using jest:
Create the file /__mocks__/knex.js in your app root:
module.exports = () => ({
select: jest.fn().mockReturnThis(),
from: jest.fn().mockReturnThis(),
where: jest.fn().mockReturnThis(),
first: jest.fn().mockReturnThis(),
then: jest.fn(function (done) {
done(null)
})
})
Pass the desired return value to done
I have been using in-memory Sqlite3 databases for automated testing with great success. It's not true unit testing however it does run much faster than MySQL or PostgreSQL. I have posted more details about this solution on a different question.
I used jest to mock knex but I had to define an object that contains the method that I used.
not the most elegant solution but is working
let knexMock = () => {
const fn = () => {
return {
returning: function() {
return {
insert: jest.fn().mockImplementation(() => [123123])
}
},
insert: jest.fn()
}
}
fn.raw = jest.fn()
return fn
}
knex.mockImplementation(knexMock)
I'm using jest and you can do something like this:
jest.mock('knex', () => {
const fn = () => {
return {
select: jest.fn().mockReturnThis(),
from: jest.fn().mockReturnThis(),
where: jest.fn().mockReturnThis(),
first: jest.fn().mockReturnThis(),
insert: jest.fn().mockReturnThis(),
raw: jest.fn().mockReturnThis(),
then: jest.fn(function (done) {
done(null)
})
}
}
return fn
})
I've written this tiny lib called knex-mock-client which does exactly this, it allows you to setup your db "connection" with a mockClient which will track your calls & help you with responses.
For example:
// my-cool-controller.ts
import { db } from '../common/db-setup';
export async function addUser(user: User): Promise<{ id }> {
const [insertId] = await db.insert(user).into('users');
return { id: insertId };
}
// my-cool-controller.spec.ts
import { expect } from '#jest/globals';
import knex, { Knex } from 'knex';
import { getTracker, MockClient } from 'knex-mock-client';
import faker from 'faker';
jest.mock('../common/db-setup', () => {
return knex({ client: MockClient });
});
describe('my-cool-controller tests', () => {
let tracker: Tracker;
beforeAll(() => {
tracker = getTracker();
});
afterEach(() => {
tracker.reset();
});
it('should add new user', async () => {
const insertId = faker.datatype.number();
tracker.on.insert('users').response([insertId]);
const newUser = { name: 'foo bar', email: 'test#test.com' };
const data = await addUser(newUser);
expect(data.id).toEqual(insertId);
const insertHistory = tracker.history.insert;
expect(insertHistory).toHaveLength(1);
expect(insertHistory[0].method).toEqual('insert');
expect(insertHistory[0].bindings).toEqual([newUser.name, newUser.email]);
});
});
This works for me, hope it helps someone:
//db.ts
import knex from 'knex';
const db = knex({
client: 'pg',
connection: {},
pool: { min: 0, max: 1 }
});
export default db('someTableName');
//myFunction.ts
//somewhere inside a function
const data = await db
// πŸ‘‡ Knex query builders are mutable so when re-using them .clone() is necessary.
.clone()
.where({ pk: 'someId', sk: 'someId2' })
.select('data')
.orderBy('inserted_at', 'desc')
.first()
//myFunction.test.ts
describe("myFunction", () => {
beforeEach(() => {
jest.spyOn(db, "clone").mockImplementation(() => db);
jest.spyOn(db, "select");
jest.spyOn(db, "where");
jest.spyOn(db, "orderBy");
});
afterEach(() => {
jest.clearAllMocks();
});
it("should work as expected", async () => {
jest.spyOn(db, "first").mockResolvedValueOnce("desiredReturnValue");
await myFunction();
expect(db.where).toHaveBeenCalledWith({
pk: "someId",
sk: "someId2",
});
expect(db.select).toHaveBeenCalledWith("data");
expect(db.orderBy).toHaveBeenCalledWith("inserted_at", "desc");
expect(db.first).toHaveBeenCalledTimes(1);
});
});

Categories

Resources