I have a basic Redux Thunk:
export const add = () => async dispatch => {
const res = await fetch("https://swapi.co/api/people/");
const res2 = await res.json();
const people = res2.results;
return dispatch({
type: "ADD",
people
});
};
I need to write a unit test for this. However, my mock appears not to have been called:
test("thunk", () => {
const dispatch = jest.fn(() => {});
add()(dispatch);
console.log(dispatch.mock.calls); // result is []
});
You need mock fetch and res.json() method too.
index.ts:
export const add = () => async dispatch => {
const res = await fetch('https://swapi.co/api/people/');
const res2 = await res.json();
const people = res2.results;
return dispatch({
type: 'ADD',
people
});
};
index.spec.ts:
import { add } from './';
describe('add', () => {
test('thunk', async () => {
const mJson = jest.fn().mockResolvedValueOnce({ results: { name: 'elsa' } });
window.fetch = jest.fn().mockResolvedValueOnce({ json: mJson });
const dispatch = jest.fn();
await add()(dispatch);
expect(dispatch).toBeCalledWith({ type: 'ADD', people: { name: 'elsa' } });
expect(window.fetch).toBeCalledWith('https://swapi.co/api/people/');
expect(mJson).toBeCalledTimes(1);
});
});
Unit test result with 100% coverage:
PASS src/stackoverflow/58595518/index.spec.ts
add
✓ thunk (9ms)
----------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
----------|----------|----------|----------|----------|-------------------|
All files | 100 | 100 | 100 | 100 | |
index.ts | 100 | 100 | 100 | 100 | |
----------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 5.866s, estimated 10s
Source code: https://github.com/mrdulin/jest-codelab/tree/master/src/stackoverflow/58595518
Related
I'm currently attempting to mock AWS SecretsManager for my unit testing with Jest, and everytime I'm hit with the ConfigError
My code is somewhat like this
//index.ts
import SM from "aws-sdk/clients/secretsmanager"
const secretManagerClient = new SM()
...
export const randomMethod = async (a: string, b: string) => {
let secret
const personalToken = {
SecretId: process.env.secretId,
}
secretManagerClient
.getSecretValue(personalToken, (err, data) => {
if (err) {
console.error(`[SECRETS MANAGER] Error fetching personal token : ${err}`)
} else if (data && data.SecretString) {
secret = data.SecretString
}
})
}
My mock goes like this :
//index.test.js
const mockGetSecretValue = jest.fn((SecretId) => {
switch (SecretId) {
case process.env.GITHUB_PERSONAL_TOKEN:
return {
SecretString: process.env.GITHUB_PERSONAL_TOKEN_VALUE,
}
default:
throw Error("secret not found")
}
})
jest.mock("aws-sdk/clients/secretsmanager", () => {
return jest.fn(() => {
return {
getSecretValue: jest.fn(({ SecretId }) => {
return mockGetSecretValue(SecretId)
}),
promise: jest.fn(),
}
})
})
However, I get this error thrown at me : ConfigError: Missing region in config, which I understand to some extent, however I don't understand why it occurs here in the mocking part...
Thanks in advance!
EDIT: Thanks to the 1st answer, I've managed to stop having this error. However, the getSecretValue() method is not returning the Secret value I want.
You should NOT use the callback of .getSecretValue() method with .promise() together. Just choose one of them. The error means you didn't mock the secretsmanager class correctly of aws-sdk.
E.g.
index.ts:
import SM from 'aws-sdk/clients/secretsmanager';
const secretManagerClient = new SM();
export const randomMethod = async () => {
const personalToken = {
SecretId: process.env.secretId || '',
};
try {
const data = await secretManagerClient.getSecretValue(personalToken).promise();
return data.SecretString;
} catch (err) {
console.error(`[SECRETS MANAGER] Error fetching personal token : ${err}`);
}
};
index.test.ts:
import { randomMethod } from '.';
import SM from 'aws-sdk/clients/secretsmanager';
import { mocked } from 'ts-jest/utils';
import { PromiseResult } from 'aws-sdk/lib/request';
jest.mock('aws-sdk/clients/secretsmanager', () => {
const mSecretManagerClient = {
getSecretValue: jest.fn().mockReturnThis(),
promise: jest.fn(),
};
return jest.fn(() => mSecretManagerClient);
});
describe('69977310', () => {
test('should get secret value', async () => {
process.env.secretId = 's1';
const mSecretManagerClient = mocked<InstanceType<typeof SM>>(new SM());
const mGetSecretValueRequest = mocked(mSecretManagerClient.getSecretValue());
mGetSecretValueRequest.promise.mockResolvedValue({
SecretString: JSON.stringify({ password: '123456' }),
} as PromiseResult<any, any>);
const actual = await randomMethod();
expect(actual).toEqual(JSON.stringify({ password: '123456' }));
expect(mSecretManagerClient.getSecretValue as jest.Mocked<any>).toBeCalledWith({ SecretId: 's1' });
});
test('should throw error', async () => {
process.env.secretId = 's1';
const logSpy = jest.spyOn(console, 'error').mockImplementation(() => 'suppress error log for testing');
const mSecretManagerClient = mocked<InstanceType<typeof SM>>(new SM());
const mGetSecretValueRequest = mocked(mSecretManagerClient.getSecretValue());
const mError = new Error('network');
mGetSecretValueRequest.promise.mockRejectedValue(mError);
await randomMethod();
expect(logSpy).toBeCalledWith(`[SECRETS MANAGER] Error fetching personal token : ${mError}`);
expect(mSecretManagerClient.getSecretValue as jest.Mocked<any>).toBeCalledWith({ SecretId: 's1' });
});
});
test result:
PASS examples/69977310/index.test.ts (7.722 s)
69977310
✓ should get secret value (4 ms)
✓ should throw error (1 ms)
----------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
----------|---------|----------|---------|---------|-------------------
All files | 100 | 50 | 100 | 100 |
index.ts | 100 | 50 | 100 | 100 | 6
----------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 8.282 s, estimated 10 s
package versions:
"aws-sdk": "^2.875.0",
"typescript": "^4.1.2",
"jest": "^26.6.3",
I've overlooked the fact that I was using a callback in order to bypass the promise().
The following is the correct code:
const mockGetSecretValue = jest.fn((SecretId, callback) => {
console.log("secretId", SecretId)
switch (SecretId) {
case process.env.GITHUB_PERSONAL_TOKEN:
const data = {
SecretString: process.env.GITHUB_PERSONAL_TOKEN_VALUE,
}
callback(null, data)
break;
default:
const err = new Error("secret not found")
throw err
}
})
jest.mock("aws-sdk/clients/secretsmanager", () => {
return jest.fn(() => {
return {
promise: jest.fn(),
getSecretValue: jest.fn(({ SecretId }, callback) => {
return mockGetSecretValue(SecretId, callback)
}),
}
})
})
Thanks again for your help #slideshowp2!
I have a DBManager class to connect to mongoClient
import { MongoClient } from 'mongodb';
class DBManager {
private url = process.env.MONGODB_URL;
private _connection: MongoClient;
constructor() {
this._connection = null;
}
get connection() {
return this._connection;
}
async start() {
if (!this._connection) {
this._connection = await MongoClient.connect(this.url);
}
}
}
export default new DBManager();
and I call this class like this
await DBManager.start();
const db = DBManager.connection.db();
I get this error when I try to mock:
Received: [TypeError: db_manager_1.default.connection.db is not a function]
this is how to mock method i use:
DBManager.start = jest.fn().mockResolvedValue(() => ({
connection: jest.fn().mockReturnThis(),
db: jest.fn().mockResolvedValue({success: true})
}));
thanks..
You can use a real MongoDB server to use in tests with the package mongodb-memory-server.
So in your case, you just need to do:
import { MongoMemoryServer } from '../index';
describe('Single MongoMemoryServer', () => {
let con;
let mongoServer;
beforeAll(async () => {
mongoServer = await MongoMemoryServer.create();
process.env.MONGODB_URL = mongoServer.getUri();
});
afterAll(async () => {
if (con) {
await con.close();
}
if (mongoServer) {
await mongoServer.stop();
}
});
it('DBManager connection', async () => {
await DBManager.start();
const db = DBManager.connection.db();
// ...
});
You can use jest.spyOn(object, methodName, accessType?) to mock DBManager.start() method and DBMananger.connection getter.
E.g.
dbManager.ts:
import { MongoClient } from 'mongodb';
class DBManager {
private url = process.env.MONGODB_URL || '';
private _connection: MongoClient | null;
constructor() {
this._connection = null;
}
get connection() {
return this._connection;
}
async start() {
if (!this._connection) {
this._connection = await MongoClient.connect(this.url);
}
}
}
export default new DBManager();
main.ts:
import DBManager from './dbManager';
export async function main() {
await DBManager.start();
const db = DBManager.connection!.db();
db.collection('users');
}
main.test.ts:
import { main } from './main';
import DBManager from './dbManager';
import { Db, MongoClient } from 'mongodb';
describe('68888424', () => {
afterEach(() => {
jest.restoreAllMocks();
});
test('should pass', async () => {
const mockDbInstance = ({
collection: jest.fn(),
} as unknown) as Db;
const mockDb = jest.fn(() => mockDbInstance);
jest.spyOn(DBManager, 'start').mockResolvedValueOnce();
jest.spyOn(DBManager, 'connection', 'get').mockReturnValue(({ db: mockDb } as unknown) as MongoClient);
await main();
expect(DBManager.start).toBeCalledTimes(1);
expect(DBManager.connection!.db).toBeCalledTimes(1);
expect(mockDbInstance.collection).toBeCalledWith('users');
});
});
test result:
PASS examples/68888424/main.test.ts (8.621 s)
68888424
✓ should pass (5 ms)
--------------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
--------------|---------|----------|---------|---------|-------------------
All files | 75 | 50 | 50 | 75 |
dbManager.ts | 57.14 | 50 | 33.33 | 57.14 | 12-17
main.ts | 100 | 100 | 100 | 100 |
--------------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 9.532 s
I have below mentioned files
|_ utils.js
|_methods.js
I am doing unit testing for rest.js methods, file contents are
methods.js
import Express from 'express'
import { add_rec } from './utils'
export const create_rec = () => async (req: Express.Request, res: Express.Response) => {
const rec_body = req.body.rec
return add_rec(rec_body)
.then((ret) => res.status(201).send(ret))
.catch((e) => {
res.status(500).send({ message: e.message })
})
}
How can mock the add_rec async function so that I can unit-test my create_rec
function
I am trying to test create_rec below way but it is not allowing me to mock add_rec method
mport { getMockReq, getMockRes } from '#jest-mock/express'
import { add_rec } from './utils'
jest.mock('./utils')
describe('test create_rec method valid param', () => {
it('test create_rec method', async () => {
const req = getMockReq({
body: {
rec: {},
},
})
const { res } = getMockRes<any>({
status: jest.fn(),
send: jest.fn(),
})
add_rec.mockResolved({}) // this line is giving error in fact it is not mocked i think
await create_rec()(req, res)
expect(res.status).toHaveBeenCalledTimes(1)
expect(res.send).toHaveBeenCalledTimes(1)
})
})
Please help me with this.
Your code is almost correct, except that you need to do some processing on the TS type of the mock method, you can use type assertion.
E.g.
methods.ts:
import Express from 'express';
import { add_rec } from './utils';
export const create_rec = () => async (req: Express.Request, res: Express.Response) => {
const rec_body = req.body.rec;
return add_rec(rec_body)
.then((ret) => res.status(201).send(ret))
.catch((e) => {
res.status(500).send({ message: e.message });
});
};
utils.ts:
export async function add_rec(params): Promise<any> {
console.log('real implementation');
}
methods.test.ts:
import Express from 'express';
import { create_rec } from './methods';
import { add_rec } from './utils';
jest.mock('./utils');
describe('68419899', () => {
test('should pass', async () => {
(add_rec as jest.MockedFunction<any>).mockResolvedValueOnce({});
const req = ({ body: { rec: {} } } as unknown) as Express.Request;
const res = ({ status: jest.fn().mockReturnThis(), send: jest.fn() } as unknown) as Express.Response;
await create_rec()(req, res);
expect(add_rec).toBeCalledWith({});
expect(res.status).toBeCalledWith(201);
expect(res.send).toBeCalledWith({});
});
});
test result:
PASS examples/68419899/methods.test.ts (8.659 s)
68419899
✓ should pass (5 ms)
------------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
------------|---------|----------|---------|---------|-------------------
All files | 81.82 | 100 | 66.67 | 75 |
methods.ts | 88.89 | 100 | 80 | 83.33 | 10
utils.ts | 50 | 100 | 0 | 50 | 2
------------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 9.198 s
I created a Joi validation schema that gets called in my routes. However when I run a code coverage that file is NOT being covered. So, I am trying to write a test for it.
Validator.js
const Joi = require('joi');
module.exports = {
validateExternalId: (schema, name) => {
return (req, res, next) => {
const result = Joi.validate({ param: req.params[name] }, schema);
if (result.error) {
return res.status(400).send(result.error.details[0].message);
}
next();
};
},
schemas: {
idSchema: Joi.object().keys({
param: Joi.string().regex(/^[a-zA-Z0-9]{20}$/).required()
})
}
};
Validator.test.js
const { validateExternalId, schemas } = require('../../src/helpers/validation');
const app = require('../../src/router')
const mockResponse = () => {
const res = {};
res.status = jest.fn().mockReturnValue(res);
res.json = jest.fn().mockReturnValue(res);
return res;
};
describe('Testing validateExternalId schema', () => {
it('It can validate the external Id Regex length', done => {
const req = {
params: [
{
extClientId: 'abcdefghij0123456789'
}
]
};
app.use('/token/:extClientId', validateExternalId(schemas.idSchema, 'extClientId');
// expect().toHaveBeenCalled();
});
});
Please Go Easy on ME... Here is my attempt on testing this Joi validator. I tried to but my expected wasn't working so I commented it out for now. any pointers would be appreciated. thank you
Here is the unit test solution:
validator.js:
const Joi = require('joi');
module.exports = {
validateExternalId: (schema, name) => {
return (req, res, next) => {
const result = Joi.validate({ param: req.params[name] }, schema);
if (result.error) {
return res.status(400).send(result.error.details[0].message);
}
next();
};
},
schemas: {
idSchema: Joi.object().keys({
param: Joi.string()
.regex(/^[a-zA-Z0-9]{20}$/)
.required(),
}),
},
};
validator.test.js:
const { validateExternalId, schemas } = require('./validator');
const Joi = require('joi');
describe('60730701', () => {
afterEach(() => {
jest.restoreAllMocks();
});
it('should send error', () => {
const validationResults = { error: { details: [{ message: 'validation error' }] } };
const validateSpy = jest.spyOn(Joi, 'validate').mockReturnValueOnce(validationResults);
const mReq = { params: { extClientId: '123' } };
const mRes = { status: jest.fn().mockReturnThis(), send: jest.fn() };
validateExternalId(schemas.idSchema, 'extClientId')(mReq, mRes);
expect(validateSpy).toBeCalledWith({ param: '123' }, schemas.idSchema);
expect(mRes.status).toBeCalledWith(400);
expect(mRes.send).toBeCalledWith('validation error');
});
it('should pass the validation and call api', () => {
const validationResults = { error: undefined };
const validateSpy = jest.spyOn(Joi, 'validate').mockReturnValueOnce(validationResults);
const mReq = { params: { extClientId: '123' } };
const mRes = {};
const mNext = jest.fn();
validateExternalId(schemas.idSchema, 'extClientId')(mReq, mRes, mNext);
expect(validateSpy).toBeCalledWith({ param: '123' }, schemas.idSchema);
expect(mNext).toBeCalled();
});
});
unit test results with 100% coverage:
PASS stackoverflow/60730701/validator.test.js (9.96s)
60730701
✓ should send error (6ms)
✓ should pass the validation and call api (2ms)
--------------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
--------------|---------|----------|---------|---------|-------------------
All files | 100 | 100 | 100 | 100 |
validator.js | 100 | 100 | 100 | 100 |
--------------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 11.647s, estimated 15s
source code: https://github.com/mrdulin/react-apollo-graphql-starter-kit/tree/master/stackoverflow/60730701
I've looked at several similar questions but none of the cases fit my problem. I'm trying to mock a constructor, which I've done in other tests, but I can't get it to work in the case of using google-auth-library
code.js
const {OAuth2Client} = require('google-auth-library');
const keys = require('./oauth2.keys.json');
async function getRedirectUrl() {
const oAuth2Client = new OAuth2Client(
keys.installed.client_id,
keys.installed.client_secret,
keys.installed.redirect_uris[0]
);
const authorizeUrl = oAuth2Client.generateAuthUrl({
access_type: 'offline',
scope: 'https://www.googleapis.com/auth/bigquery',
prompt: 'consent'
});
return authorizeUrl;
}
test.js
let Code = require('../code.js');
describe('code', function() {
let generateUrlStub, tokenStub, mockClient;
before(async () => {
generateUrlStub = sinon.stub().returns('http://example.com');
tokenStub = sinon.stub().returns({tokens: 'tokens'});
mockClient = sinon.stub().returns({
generateAuthUrl: generateUrlStub,
getToken: tokenStub,
});
Code = proxyquire('../Code.js', {
'google-auth-library': mockClient,
});
});
it('should call generateAuthUrl', async function() {
const output = await Code.getRedirectUrl();
sinon.assert.called(generateUrlStub)
});
});
Here is the unit test solution:
const { OAuth2Client } = require("google-auth-library");
const keys = {
installed: {
client_id: "1",
client_secret: "client_secret",
redirect_uris: ["http://example.com/callback"]
}
};
async function getRedirectUrl() {
const oAuth2Client = new OAuth2Client(
keys.installed.client_id,
keys.installed.client_secret,
keys.installed.redirect_uris[0]
);
const authorizeUrl = oAuth2Client.generateAuthUrl({
access_type: "offline",
scope: "https://www.googleapis.com/auth/bigquery",
prompt: "consent"
});
return authorizeUrl;
}
module.exports = { getRedirectUrl };
index.spec.js:
const proxyquire = require("proxyquire");
const sinon = require("sinon");
const { expect } = require("chai");
describe("code", function() {
let generateUrlStub, tokenStub, code;
beforeEach(() => {
generateUrlStub = sinon.stub().returns("http://example.com");
tokenStub = sinon.stub().returns({ tokens: "tokens" });
code = proxyquire("./", {
"google-auth-library": {
OAuth2Client: sinon.stub().callsFake(() => {
return {
generateAuthUrl: generateUrlStub,
getToken: tokenStub
};
})
}
});
});
afterEach(() => {
sinon.restore();
});
it("should call generateAuthUrl", async function() {
const output = await code.getRedirectUrl();
expect(output).to.be.eq("http://example.com");
sinon.assert.called(generateUrlStub);
});
});
Unit test result with 100% coverage:
code
✓ should call generateAuthUrl
1 passing (216ms)
---------------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
---------------|----------|----------|----------|----------|-------------------|
All files | 100 | 100 | 100 | 100 | |
index.js | 100 | 100 | 100 | 100 | |
index.spec.js | 100 | 100 | 100 | 100 | |
---------------|----------|----------|----------|----------|-------------------|
Source code: https://github.com/mrdulin/mocha-chai-sinon-codelab/tree/master/src/stackoverflow/58955304