Update January 22nd 2020
The solution from #slideshowp2 is correct, but I could not get it to work at all, due to this TypeError:
TypeError: Cannot read property 'query' of undefined
Well it turned out to be my jest configuration that had resetMocks: true set. After I removed it, the test did pass. (I don't know why though)
Original question:
I need to execute a graphql query in a helper function outside of a React component using Apollo Client and after a bit of trial and error I went for this approach which is working as it is supposed to:
setup.ts
export const setupApi = (): ApolloClient<any> => {
setupServiceApi(API_CONFIG)
return createServiceApolloClient({ uri: `${API_HOST}${API_PATH}` })
}
getAssetIdFromService.ts
import { setupApi } from '../api/setup'
const client = setupApi()
export const GET_ASSET_ID = gql`
query getAssetByExternalId($externalId: String!) {
assetId: getAssetId(externalId: $externalId) {
id
}
}
`
export const getAssetIdFromService = async (externalId: string) => {
return await client.query({
query: GET_ASSET_ID,
variables: { externalId },
})
return { data, errors, loading }
}
Now I am trying to write test tests for the getAssetIdFromService function, but I have trouble figuring out how to get the client.query method to work in tests.
I have tried the approach below including many others that did not work.
For this particular setup, jest throws
TypeError: client.query is not a function
import { setupApi } from '../../api/setup'
import { getAssetIdFromService } from '../getAssetIdFromService'
jest.mock('../../api/setup', () => ({
setupApi: () => jest.fn(),
}))
describe('getAssetIdFromService', () => {
it('returns an assetId when passed an externalId and the asset exists in the service', async () => {
const { data, errors, loading } = await getAssetIdFromService('e1')
// Do assertions
})
}
I assume I am missing something in relation to this part:
jest.mock('../../api/setup', () => ({
setupApi: () => jest.fn(),
}))
...but I cannot see it.
You didn't mock correctly. Here is the correct way:
getAssetIdFromService.ts:
import { setupApi } from './setup';
import { gql } from 'apollo-server';
const client = setupApi();
export const GET_ASSET_ID = gql`
query getAssetByExternalId($externalId: String!) {
assetId: getAssetId(externalId: $externalId) {
id
}
}
`;
export const getAssetIdFromService = async (externalId: string) => {
return await client.query({
query: GET_ASSET_ID,
variables: { externalId },
});
};
setup.ts:
export const setupApi = (): any => {};
getAssetIdFromService.test.ts:
import { getAssetIdFromService, GET_ASSET_ID } from './getAssetIdFromService';
import { setupApi } from './setup';
jest.mock('./setup.ts', () => {
const mApolloClient = { query: jest.fn() };
return { setupApi: jest.fn(() => mApolloClient) };
});
describe('59829676', () => {
it('should query and return data', async () => {
const client = setupApi();
const mGraphQLResponse = { data: {}, loading: false, errors: [] };
client.query.mockResolvedValueOnce(mGraphQLResponse);
const { data, loading, errors } = await getAssetIdFromService('e1');
expect(client.query).toBeCalledWith({ query: GET_ASSET_ID, variables: { externalId: 'e1' } });
expect(data).toEqual({});
expect(loading).toBeFalsy();
expect(errors).toEqual([]);
});
});
Unit test results with 100% coverage:
PASS apollo-graphql-tutorial src/stackoverflow/59829676/getAssetIdFromService.test.ts (8.161s)
59829676
✓ should query and return data (7ms)
--------------------------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
--------------------------|----------|----------|----------|----------|-------------------|
All files | 100 | 100 | 100 | 100 | |
getAssetIdFromService.ts | 100 | 100 | 100 | 100 | |
--------------------------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 8.479s
Source code: https://github.com/mrdulin/apollo-graphql-tutorial/tree/master/src/stackoverflow/59829676
Use blow mock class:
class ApolloClient {
constructor(uri: string, fetch: any, request: any) {}
setupApi() {
return {
query: jest.fn(),
};
}
query() {
return jest.fn();
}
}
module.exports = ApolloClient;
and add below line to jest.cofig.ts
moduleNameMapper: {
'apollo-boost': '<rootDir>/.jest/appolo-client.ts',
},
Related
I have a small issue with a unit test I wrote for a controller method.
Short version:
return this.userPreferencesService.createPreferences(eUserId, userPreferences);
Long version:
I get this error:
UserPreferencesController › createUserPreferences › should create a new userPreferences
TypeError: this.userPreferencesService.createPreferences is not a function
31 | userPreferences: TestUserPreferencesDto,
32 | ): Promise<UserPreferences> {
> 33 | return this.userPreferencesService.createPreferences(eUserId, userPreferences);
| ^
34 | }
35 |
36 | /**
at UserPreferencesController.createPreferences (user-preferences/user-preferences.controller.ts:33:44)
at Object.<anonymous> (user-preferences/user-preferences.controller.spec.ts:67:45)
The toBeDefined passes but the createUserPreferences fails for the error above.
The code works great and there only the test fails.
I just can't find the reason this is not a function?
service file content (relevant data only):
#Injectable()
export class UserPreferencesService {
constructor(#InjectModel('UserPreferences') private userPreferencesModel: Model<UserPreferences>) {
}
/**
* ADD a single user preferences by id => POST api/v1/user-preferences/
* #param userPreferences
*/
async createPreferences(eUserId: string, userPreferences: TestUserPreferencesDto): Promise<UserPreferences> {
Object.assign(userPreferences, {eUserId: eUserId});
return this.userPreferencesModel.create(userPreferences);
}
This is the controller (relevant data only)
#Controller('v1/user-preferences')
export class UserPreferencesController {
constructor(private userPreferencesService: UserPreferencesService) {}
/**
* Add user preferences to the database
* #param userPreferences
*/
#Post()
async createPreferences(
#Headers('x-e-user-id') eUserId: string,
#Body()
userPreferences: TestUserPreferencesDto,
): Promise<UserPreferences> {
return this.userPreferencesService.createPreferences(eUserId, userPreferences);
}
This is the entire test file:
import {Test, TestingModule} from '#nestjs/testing';
import { getModelToken } from '#nestjs/mongoose';
import { Model } from 'mongoose';
import {UserPreferencesController} from './user-preferences.controller';
import {UserPreferencesService} from './user-preferences.service';
import { exitToOptions } from './schemas/user-preferences.schema';
const ReturnedUserPreferencesMock = {
_id: '62a161a9654a511b28e6f3db',
eUserId: '123456',
uiTheme: 'dark',
panelWidth: 300,
editingHandles: true,
enableLightboxInEditor: true,
hiddenElements: true,
defaultDeviceView: 'mobile',
exitTo: exitToOptions.DASHBOARD,
};
const eUserIdeMock = '123456';
const userPreferencesMock = {
uiTheme: 'dark',
panelWidth: 327,
editingHandles: true,
enableLightboxInEditor: true,
hiddenElements: true,
defaultDeviceView: 'mobile',
exitTo: exitToOptions.DASHBOARD,
}
const mockUserPreferencesService = {
create: jest.fn().mockResolvedValueOnce(ReturnedUserPreferencesMock),
}
describe('UserPreferencesController', () => {
let controller: UserPreferencesController;
let service: UserPreferencesService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [UserPreferencesController],
providers: [{
provide: UserPreferencesService,
useValue: mockUserPreferencesService
}]
}).compile();
controller = module.get<UserPreferencesController>(UserPreferencesController);
service = module.get<UserPreferencesService>(UserPreferencesService);
});
it('Controler should be defined', () => {
expect(controller).toBeDefined();
});
describe('createUserPreferences', () => {
it('should create a new userPreferences', async () => {
const result = await controller.createPreferences(eUserIdeMock, userPreferencesMock);
expect(service.createPreferences).toHaveBeenCalledWith(eUserIdeMock, userPreferencesMock);
expect(result).toEqual(ReturnedUserPreferencesMock);
});
});
});
The mockUserPreferencesService you provide does not have a createPreferences method, only a create. You need to provide an implementation for each method you are going to call of the original service for your mock.
const mockUserPreferencesService = {
create: jest.fn().mockResolvedValueOnce(ReturnedUserPreferencesMock),
createPreferences: jest.fn().mockResolvedValueOnce(WhateverObjectShouldGoHere),
}
I have the below Middleware class which I want to unit test:
const jwt = require('jsonwebtoken');
const config = require('../config/auth.config.js');
const db = require('../models');
const User = db.user;
const Role = db.role;
isAdmin = (req, res, next) => {
User.findById(req.userId).exec((err, user) => {
if (err) {
res.status(500).send({ message: err });
return;
}
Role.find(
{
_id: { $in: user.roles }
},
(err, roles) => {
console.log('Made it here Role');
console.log(JSON.stringify(roles));
console.log(roles);
if (err) {
console.log(err);
res.status(500).send({ message: err });
return;
}
for (let i = 0; i < roles.length; i++) {
if (roles[i].name === 'admin') {
next();
return;
}
}
res.status(403).send({ message: 'Require Admin Role!' });
return;
}
);
});
};
I'm able to mock the User.findById, the config the jsonwebtoken, but I'm not able to correctly stub the Role.find(...
This is my current test spec using Mocha and Chai, Sinon and Proxyquire
const chai = require('chai');
const proxyquire = require('proxyquire');
const sinon = require('sinon');
const mongoose = require('mongoose');
chai.should();
var expect = chai.expect;
describe('Verify AuthJWT class', () => {
let mockAuthJwt;
let userStub;
let roleStub;
let configStub;
let jwtStub;
let json;
let err;
let json1;
let err1;
before(() => {
userStub = {
exec: function (callback) {
callback(err, json);
}
};
roleStub = {
find: function (query, callback) {
console.log('Heree');
return callback(err1, json1);
}
};
configStub = {
secret: 'my-secret'
};
jwtStub = {
verify: function (token,
secretOrPublicKey, callback) {
return callback(err, json);
}
};
mockAuthJwt = proxyquire('../../../middlewares/authJwt.js',
{
'User': sinon.stub(mongoose.Model, 'findById').returns(userStub),
'db.role': sinon.stub().returns(roleStub),
'../config/auth.config.js': configStub,
'jsonwebtoken': jwtStub
}
);
});
describe('isAdmin function', () => {
it('should Pass when user is Admin', (done) => {
err = null;
json = { roles: ['5ef3bd3f4144ae5898347e4e'] };
err1 = {};
json1 = [{ _id: '5ef3bd3f4144ae5898347e4e', name: 'admin', __v: 0 }];
let fakeRes = {
status: sinon.stub().returnsThis(),
send: sinon.stub()
};
let fakeReq = {
body: { userId: '123', email: 'test#test.com', roles: ['admin'] }
};
let fakeNext = sinon.spy();
mockAuthJwt.isAdmin(fakeReq, fakeRes, fakeNext);
expect(fakeNext.calledOnce).to.be.true;
console.log('Status ' + fakeRes.status.firstCall.args[0]);
done();
});
Any inside on how to correctly use the proxyquire mock and stub the Role.find method so I can unit test the function correctly.
Based on your case (unit test), you do not need proxyquire at all. You just need chai and sinon.
This is simplified example on how can be done.
File middleware.js (just for example file name)
// #file: middleware.js (This is line 1)
const db = require('./models'); // Fake model.
const isAdmin = (req, res, next) => {
const User = db.user; // Define it inside.
const Role = db.role; // Define it inside.
User.findById(req.userId).exec((err1, user) => {
if (err1) {
res.status(500).send({ message: err1 });
return;
}
Role.find({ _id: { $in: user.roles } }, (err2, roles) => {
if (err2) {
res.status(500).send({ message: err2 });
return;
}
for (let i = 0; i < roles.length; i += 1) {
if (roles[i].name === 'admin') {
next();
return;
}
}
res.status(403).send({ message: 'Require Admin Role!' });
});
});
};
module.exports = { isAdmin };
File test / spec: isAdmin.test.js
const { expect } = require('chai');
const sinon = require('sinon');
// Load module under test.
const middleware = require('./middleware');
// Load module db to create stubs.
const db = require('./models');
describe('Verify AuthJWT class', function () {
describe('isAdmin function', function () {
it('should Pass when user is Admin', function (done) {
// Fake result user findById.
const fakeUser = { roles: ['5ef3bd3f4144ae5898347e4e'] };
const fakeErrUser = null;
// Create stub for User findById.
const stubUserFindByID = sinon.stub(db.user, 'findById');
stubUserFindByID.returns({
exec: (arg1) => {
// Inject fakeErrUser and fakeUser result here.
arg1(fakeErrUser, fakeUser);
},
});
// Fake result role find.
const fakeRole = [{ _id: '5ef3bd3f4144ae5898347e4e', name: 'admin', __v: 0 }];
const fakeErrRole = null;
// Create stub for Role find.
const stubRoleFind = sinon.stub(db.role, 'find');
stubRoleFind.callsFake((arg1, arg2) => {
// Inject fakeErrRole and fakeRole result here.
arg2(fakeErrRole, fakeRole);
});
// Create fake response: empty object because no activity.
const fakeRes = {};
// Create fake request.
// Note: I remove body property!
const fakeReq = { userId: '123', email: 'test#test.com', roles: ['admin'] };
// Create fake for next function (fake is sufficient).
const fakeNext = sinon.fake();
// Call function under test.
middleware.isAdmin(fakeReq, fakeRes, fakeNext);
// Verify stub user findById get called once.
expect(stubUserFindByID.calledOnce).to.equal(true);
// Make sure stub user findById called once with correct argument.
expect(stubUserFindByID.calledOnceWith(fakeReq.userId)).to.equal(true);
// Verify stub role find get called once.
expect(stubRoleFind.calledOnce).to.equal(true);
// Make sure stub role find called with correct argument.
// Note: alternative style.
expect(stubRoleFind.args[0][0]).to.deep.equal({
// Query use fakeUser result.
_id: { $in: fakeUser.roles },
});
// Finally for this case: make sure fakeNext get called.
expect(fakeNext.calledOnce).to.equal(true);
// Do not forget to restore the stubs.
stubUserFindByID.restore();
stubRoleFind.restore();
done();
});
});
});
Run it using nyc (to check coverage) and mocha (test runner).
$ npx nyc mocha isAdmin.test.js --exit
Verify AuthJWT class
isAdmin function
✓ should Pass when user is Admin
1 passing (7ms)
---------------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
---------------|----------|----------|----------|----------|-------------------|
All files | 77.27 | 50 | 60 | 76.19 | |
middleware.js | 73.68 | 50 | 100 | 72.22 | 10,11,15,16,26 |
models.js | 100 | 100 | 0 | 100 | |
---------------|----------|----------|----------|----------|-------------------|
$
That test case only cover success condition (next function get called). I hope the example clear enough and you can continue to create test cases to fully cover function isAdmin based on my example above. Only 3 cases left. Good luck!
I am new to passport.js and trying to cover the unit test case for my JWT strategy. Can anyone suggest how to do that?
// Setup JWT strategy for all requests
passport.use(
new JWTStrategy(
{
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: JWT_PRIVATE_KEY,
},
async (jwtPayload: any, done: any) => {
const isUser = jwtPayload.type === EntityType.User;
const model = isUser ? userModel : vendorModel;
try {
const document = await model.findOne({ _id: jwtPayload.id });
if (document) {
return done(null, jwtPayload);
} else {
return done(null, false);
}
} catch (err) {
return done(err, false);
}
},
),
);
Unit test solution:
index.ts:
import passport from 'passport';
import { Strategy as JWTStrategy, ExtractJwt } from 'passport-jwt';
import { userModel, vendorModel, EntityType } from './models';
const JWT_PRIVATE_KEY = 'secret 123';
passport.use(
new JWTStrategy(
{
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: JWT_PRIVATE_KEY,
},
async (jwtPayload: any, done: any) => {
console.log('123123');
const isUser = jwtPayload.type === EntityType.User;
const model = isUser ? userModel : vendorModel;
try {
const document = await model.findOne({ _id: jwtPayload.id });
if (document) {
return done(null, jwtPayload);
} else {
return done(null, false);
}
} catch (err) {
return done(err, false);
}
},
),
);
models.ts:
export enum EntityType {
User = 'User',
}
export const userModel = {
async findOne(opts) {
return 'real user document';
},
};
export const vendorModel = {
async findOne(opts) {
return 'real vendor document';
},
};
index.test.ts:
import { Strategy as JWTStrategy, ExtractJwt, VerifyCallback, StrategyOptions } from 'passport-jwt';
import passport from 'passport';
import { userModel, vendorModel } from './models';
jest.mock('passport-jwt', () => {
const mJWTStrategy = jest.fn();
const mExtractJwt = {
fromAuthHeaderAsBearerToken: jest.fn(),
};
return { Strategy: mJWTStrategy, ExtractJwt: mExtractJwt };
});
jest.mock('passport', () => {
return { use: jest.fn() };
});
describe('62125872', () => {
let verifyRef;
beforeEach(() => {
const mJwtFromRequestFunction = jest.fn();
(ExtractJwt.fromAuthHeaderAsBearerToken as jest.MockedFunction<
typeof ExtractJwt.fromAuthHeaderAsBearerToken
>).mockReturnValueOnce(mJwtFromRequestFunction);
(JWTStrategy as jest.MockedClass<any>).mockImplementation((opt: StrategyOptions, verify: VerifyCallback) => {
verifyRef = verify;
});
});
it('should verify using user model and call done with jwtpayload if user document exists', async () => {
const payload = { type: 'User', id: 1 };
const mDone = jest.fn();
jest.spyOn(userModel, 'findOne').mockResolvedValueOnce('mocked user document');
await import('./');
await verifyRef(payload, mDone);
expect(passport.use).toBeCalledWith(expect.any(Object));
expect(JWTStrategy).toBeCalledWith(
{ jwtFromRequest: expect.any(Function), secretOrKey: 'secret 123' },
expect.any(Function),
);
expect(ExtractJwt.fromAuthHeaderAsBearerToken).toBeCalledTimes(1);
expect(userModel.findOne).toBeCalledWith({ _id: 1 });
expect(mDone).toBeCalledWith(null, { type: 'User', id: 1 });
});
it("should verify using user model and call done with false if user document doesn't exist", async () => {
const payload = { type: 'User', id: 1 };
const mDone = jest.fn();
jest.spyOn(userModel, 'findOne').mockResolvedValueOnce('');
await import('./');
await verifyRef(payload, mDone);
expect(passport.use).toBeCalledWith(expect.any(Object));
expect(JWTStrategy).toBeCalledWith(
{ jwtFromRequest: expect.any(Function), secretOrKey: 'secret 123' },
expect.any(Function),
);
expect(ExtractJwt.fromAuthHeaderAsBearerToken).toBeCalledTimes(1);
expect(userModel.findOne).toBeCalledWith({ _id: 1 });
expect(mDone).toBeCalledWith(null, false);
});
// you can do the rest parts
});
Unit test results:
PASS stackoverflow/62125872/index.test.ts
62125872
✓ should verify using user model and call done with jwtpayload if user document exists (11ms)
✓ should verify using user model and call done with false if user document doesn't exist (2ms)
-----------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
-----------|---------|----------|---------|---------|-------------------
All files | 85 | 83.33 | 60 | 84.21 |
index.ts | 92.86 | 75 | 100 | 92.31 | 24
models.ts | 66.67 | 100 | 33.33 | 66.67 | 6,11
-----------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 3.716s, estimated 10s
Using supertest to verify full cycle
import request from 'supertest';
import express from 'express';
import jwt from 'jsonwebtoken'
export const createAuthToken = (userId) => {
const body = {
type: EntityType.User,
id: userId,
};
return jwt.sign(body, JWT_PRIVATE_KEY);
};
// this function should configure express app
const appLoader = async app => {
(await import('../app/loaders/express')).expressLoader({ app }); // express bindings and routes
await import('./'); // passport config
}
describe('passport-jwt auth', () => {
const app = express();
const token = createAuthToken('user1')
beforeAll(async () => {
await appLoader({ app });
});
it('should verify auth', async () => {
jest.spyOn(userModel, 'findOne').mockResolvedValueOnce('mocked user document');
await request(app)
.get('/protected-endpoint')
.set('Authorization', `Bearer ${token}`)
.expect(200);
});
it('should verify auth - failure', async () => {
await request(app)
.get('/protected-endpoint')
.set('Authorization', `Bearer wrong-token`)
.expect(401);
});
});
I am having a Typescript backend structure and I want to create unit tests for all the functionalities. I am using JEST and aws-skd-mock for mocking AWS. I have tried some things but it seems I am not doing the right thing.
I have this service where I am getting a parameter from ParamterStore (amazon.service.ts):
import * as AWS from "aws-sdk";
export class AmazonService {
parameterStore: AWS.SSM;
constructor() {
this.parameterStore = new AWS.SSM();
}
async getParam(param) {
let self = this;
console.log('IN getPARAM', param);
return new Promise(function(resolve, reject){
self.parameterStore.getParameter({
Name: param,
WithDecryption: true
}, function (err, data) {
if (err) {
console.log('Error ', err);
return resolve({Error: 'ParameterNotFound'})
}
console.log('RES ', data.Parameter.Value);
return resolve(data.Parameter.Value)
})
})
}
}
Then, I mock whole amazon.service file, I mock SSM.getParameter with response in my test file (amazon.service.spect.ts):
import * as AWSMock from "aws-sdk-mock";
import * as AWS from "aws-sdk";
import {AmazonService} from "./amazon.service";
jest.mock('./amazon.service');
describe('amazon service mock', () => {
let amazonService: AmazonService;
it('should get Parameter from Parameter Store', async () => {
const ssmGetParameterPromise = jest.fn().mockReturnValue({
promise: jest.fn().mockResolvedValue({
Parameter: {
Name: 'NAME',
Type: 'SecureString',
Value: 'VALUE',
Version: 1,
LastModifiedDate: 1546551668.495,
ARN: 'arn:aws:ssm:eu-test-1:123:NAME'
}
})
});
AWSMock.setSDKInstance(AWS);
AWSMock.mock('SSM', 'GetParameter', ssmGetParameterPromise);
amazonService = new AmazonService();
console.log(await amazonService.getParam('NAME'))
await expect(amazonService.getParam('NAME')).resolves.toBe('VALUE')
})
});
With this I get undefined when amazonService.getParam is called.
As I looked in the examples they are initializing new AWS.SSM() right after is mocked and call it from test, but I want to achieve that by calling my function. It seems like SSM is not mocked when my function is called.
Any suggestions how to do this right ?
You don't need to mock ./amazon.service.ts module. Here is the unit test solution without using aws-sdk-mock.
E.g.
amazon.service.ts:
import * as AWS from 'aws-sdk';
export class AmazonService {
parameterStore: AWS.SSM;
constructor() {
this.parameterStore = new AWS.SSM();
}
async getParam(param) {
let self = this;
console.log('IN getPARAM', param);
return new Promise(function (resolve, reject) {
self.parameterStore.getParameter(
{
Name: param,
WithDecryption: true,
},
function (err, data) {
if (err) {
console.log('Error ', err);
return resolve({ Error: 'ParameterNotFound' });
}
console.log('RES ', data.Parameter!.Value);
return resolve(data.Parameter!.Value);
},
);
});
}
}
amazon.service.spec.ts:
import * as AWS from 'aws-sdk';
import { AmazonService } from './amazon.service';
import { mocked } from 'ts-jest/utils';
import { AWSError } from 'aws-sdk';
import { GetParameterResult } from 'aws-sdk/clients/ssm';
jest.mock('aws-sdk', () => {
const mSSMInstance = {
getParameter: jest.fn(),
};
const mSSM = jest.fn(() => mSSMInstance);
return { SSM: mSSM };
});
describe('amazon service mock', () => {
let amazonService: AmazonService;
it('should get Parameter from Parameter Store', async () => {
amazonService = new AmazonService();
expect(AWS.SSM).toBeCalled();
const mSSMInstance = new AWS.SSM();
const mData = {
Parameter: {
Name: 'NAME',
Type: 'SecureString',
Value: 'VALUE',
Version: 1,
LastModifiedDate: new Date(1995, 11, 17),
ARN: 'arn:aws:ssm:eu-test-1:123:NAME',
},
};
mocked(mSSMInstance.getParameter).mockImplementationOnce(
(params, callback?: (err: AWSError | null, data: GetParameterResult) => void): any => {
if (callback) {
callback(null, mData);
}
},
);
const actual = await amazonService.getParam('NAME');
expect(actual).toBe('VALUE');
});
});
unit test result with coverage report:
PASS stackoverflow/61871955/amazon.service.spec.ts (9.613s)
amazon service mock
✓ should get Parameter from Parameter Store (19ms)
console.log
IN getPARAM NAME
at AmazonService.<anonymous> (stackoverflow/61871955/amazon.service.ts:12:13)
console.log
RES VALUE
at stackoverflow/61871955/amazon.service.ts:24:19
-------------------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
-------------------|---------|----------|---------|---------|-------------------
All files | 86.67 | 50 | 100 | 85.71 |
amazon.service.ts | 86.67 | 50 | 100 | 85.71 | 21-22
-------------------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 10.925s
I have the following code in javascript:
class SampleClass {
constructor(param1) {
this.param1 = param1;
this.init();
}
async init() {
await this.getData();
this.loadIframe();
}
async getData() {
const response = fetch(url);
const data = response.json();
//set the response to class variable
this.param2 = data;
}
loadIframe() {
//some logic to load iframe.
}
}
What would be the best approach to test this using jest?
Currently I am testing the constructor logic by mocking the init() function.
But I also have to test the getData() function. What should be the approach to test the getData() method.
I tried testing the getData() function by not mocking the init() function and using async testing, but I am not sure where to use the await in the test as the function is nested and called from within the init which is called from constructor.
it('should fetch data', async()=>{
//some logic
})
You can use jest.spyOn(object, methodName) to mock your methods of the SampleClass. My test environment is node, so I mock fetch method on global object. If your test environment is browser, the fetch method is on window object.
E.g.
sampleClass.js:
class SampleClass {
constructor(param1) {
this.param1 = param1;
this.init();
}
async init() {
await this.getData();
this.loadIframe();
}
async getData() {
const url = 'https://stackoverflow.com/';
const response = fetch(url);
const data = response.json();
this.param2 = data;
}
loadIframe() {}
}
export { SampleClass };
sampleClass.test.js:
import { SampleClass } from './sampleClass';
describe('60146073', () => {
afterEach(() => {
jest.restoreAllMocks();
});
describe('#constructor', () => {
it('should consturt', () => {
jest.spyOn(SampleClass.prototype, 'constructor');
jest.spyOn(SampleClass.prototype, 'init').mockReturnValueOnce();
const instance = new SampleClass('param1');
expect(instance.param1).toBe('param1');
expect(instance.init).toBeCalledTimes(1);
});
});
describe('#init', () => {
it('should init', async () => {
jest.spyOn(SampleClass.prototype, 'getData').mockResolvedValueOnce();
jest.spyOn(SampleClass.prototype, 'loadIframe').mockReturnValueOnce();
jest.spyOn(SampleClass.prototype, 'init').mockReturnValueOnce();
const instance = new SampleClass();
await instance.init();
expect(instance.getData).toBeCalledTimes(1);
expect(instance.loadIframe).toBeCalledTimes(1);
});
});
describe('#getData', () => {
it('should fetch data', async () => {
const mResponse = { json: jest.fn().mockReturnValueOnce({}) };
global.fetch = jest.fn().mockResolvedValueOnce(mResponse);
jest.spyOn(SampleClass.prototype, 'init').mockReturnValueOnce();
const instance = new SampleClass();
await instance.getData();
expect(global.fetch).toBeCalledWith('https://stackoverflow.com/');
expect(mResponse.json).toBeCalledTimes(1);
});
});
});
Unit test results with 100% coverage:
PASS stackoverflow/60146073/sampleClass.test.js
60146073
#constructor
✓ should consturt (3ms)
#init
✓ should init (2ms)
#getData
✓ should fetch data (2ms)
----------------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
----------------|---------|----------|---------|---------|-------------------
All files | 100 | 100 | 80 | 100 |
sampleClass.js | 100 | 100 | 80 | 100 |
----------------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 3 passed, 3 total
Snapshots: 0 total
Time: 4.137s, estimated 5s
Source code: https://github.com/mrdulin/react-apollo-graphql-starter-kit/tree/master/stackoverflow/60146073