How to make stub for mongoose document object - unit-testing

I am writing unit test for my auth.service module validateReader unit,
async validateReader(username: string, password: string): Promise<any> {
const reader = await this.readerService.findOne(username);
const match = await bcrypt.compare(password, reader.password);
if (match) {
const { password, ...result } = reader.toJSON();
this.logger.info(
`Reader ${reader.username} username & password validation passed`,
);
return result;
}
this.logger.warn(`Incorrect password in reader ${reader.username} login`);
return null;
}
I tried to mock readerService.findOne function as following:
jest
.spyOn(readerService, 'findOne')
.mockImplementationOnce(() => Promise.resolve(readerStub()));
but did not work, always got error - Cannot spy the findOne property because it is not a function; I think the reason is the returned value must be mongoose document object (need toJSON() method), but my readerStub() just return a reader object, missing lots of document properties. Is there anyway I can set up stub for document & reader? And maybe my analysis is wrong, there is other reason to got this error.
Following is my mock readerService:
export const ReaderService = jest.fn().mockReturnValue({
register: jest.fn().mockResolvedValue(readerStub()),
findOne: jest.fn().mockResolvedValue(readerStub()),
getProfile: jest.fn().mockResolvedValue(readerStub()),
updateProfile: jest.fn().mockResolvedValue(readerStub()._id),
changePwd: jest.fn().mockResolvedValue(readerStub().username),
login: jest.fn().mockResolvedValue(accessTokenStub()),
tokenRefresh: jest.fn().mockReturnValue(accessTokenStub()),
logout: jest.fn().mockResolvedValue(readerStub()._id),
});

Related

How to set/mock an env variable in vitest (`process.env.NODE_ENV = 'anything'` takes effect only in test file)?

I have a method, in a class, that only executes its action when NODE_ENV === 'test'.
Here is the test that I set the env to anything to test the failing scenario:
it('returns Left on clearDatabase when not in test environment', async () => {
const { sut } = await makeSut()
process.env.NODE_ENV = 'any_environment'
const result = await sut.clearDatabase()
process.env.NODE_ENV = 'test'
expect(result.isLeft()).toBe(true)
})
Here is the method:
async clearDatabase (): Promise<Either<Error, void>> {
if (process.env.NODE_ENV !== 'test') {
return left(new Error('Clear database is allowed only in test environment'))
}
try {
const { database } = this.props.dataSource
await this.mongoClient.db(database).dropDatabase()
return right()
} catch (error) {
return left(error)
}
}
The problem is that when the method do it's verification, the value in NODE_ENV wasn't changed at all, it has its initial value (test). If I log the value, after setting it, in test file it's there, only the object can't see this change. In jest it works just fine. How can I set/mock it properly in vitest?
Here you find a StackBlitz with an example scenario: https://stackblitz.com/edit/node-lr72gz?file=test/example.unit.test.ts&view=editor

Unit Testing Passport Strategy / NestJS Guard

I have a JWTGuard() decorator which follows the implementation as described in https://docs.nestjs.com/security/authentication#jwt-functionality
It looks like so:
import { ExecutionContext, Injectable } from '#nestjs/common';
import { Reflector } from '#nestjs/core';
import { AuthGuard } from '#nestjs/passport';
import { IS_PUBLIC_ROUTE } from '../decorators/public-route.decorator';
#Injectable()
export class JWTAuthenticationGuard extends AuthGuard('jwt') {
constructor(private readonly reflector: Reflector) {
super();
}
canActivate(context: ExecutionContext): Promise<boolean> {
const isPublic = this.reflector.getAllAndOverride<boolean>(
IS_PUBLIC_ROUTE,
[context.getHandler(), context.getClass()],
);
if (isPublic) return true;
return super.canActivate(context)
}
}
It works as expected, but having difficulty unit testing the guard. In short, I want to test three things:
If IS_PUBLIC_ROUTE is set, then canActivate() returns true ✅ this unit test works
If an expired/invalid/incorrectly signed token is passed, canActivate() returns false ❌
If a valid token is found, canActivate() returns true ❌
I tried to follow https://github.com/jmcdo29/testing-nestjs/blob/master/apps/complex-sample/src/cat/cat.guard.spec.ts which just tests a bare guard, not extending a Passport Guard.
Tests are setup as follows:
let guard: JWTAuthenticationGuard;
const reflector = new Reflector();
beforeEach(() => {
guard = new JWTAuthenticationGuard(reflector);
passport.use('jwt', new JWTStrategy());
});
The first test (should return false)
it('should return false if no authorization token is found', () => {
const context = createMock<ExecutionContext>();
const result = guard.canActivate(context);
expect(result).toBe(false);
});
My test never even makes it to the expect() clause, as it has thrown an UnauthorisedException and the test suite exits entirely.
I tried debugging inside the canActivate() method, and checking the type of super.canActivate() which tells me that it's a pending promise. I tried awaiting it, but still says its Pending, I tried wrapping in a try/catch, but the catch block never runs. Reading through the code of passport's AuthGuard, it never throws either.
I tried to add a valid token:
it('should return false if no authorization token is found', async () => {
const context = createMock<ExecutionContext>();
expect(context.switchToHttp()).toBeDefined();
context.switchToHttp().getRequest.mockReturnValue({
headers: {
Authorization:
'Bearer ey...[truncated]...dE',
},
});
const result = await guard.canActivate(context);
expect(result).toBe(true);
however I still just get an UnauthorisedException thrown from god knows where at the result = guard.can... line, and still never makes it to the expect.
Any guidance would be appriciated.

How to use onCall with aws-sdk-mock?

I would like let the mock method enable different responses for consecutive calls to the same method.
I found that Sinon has onCall, it allowed I can stub method like below,
let stubCall = sandbox.stub(Math, 'random');
stubCall.onCall(0).returns(Promise.resolve(0));
stubCall.onCall(1).returns(Promise.resolve(-1));
but I don't know how to let this work on AWS mock framework like this.
AWS.mock('CloudFormation', 'describeStacks', Promise.resolve(stackResponse));
I tried
AWS.mock('CloudFormation', 'describeStacks', Promise.resolve(stackResponse)).onCall(0).returns(Promise.resolve(res));
and
let mockCall = AWS.mock('CloudFormation', 'describeStacks', Promise.resolve(res));
mockCall.onCall(0).returns(Promise.resolve(res));
both of them didn't work.
I found people discuss this issue , mentioned since this aws-mock use sinon, it should able to use onCall. Is anyone use it successfully?
Since I use promise, I don't know what else I can do to return the different response for the same method has been called several times.
First, set the AWS SDK instance to be mocked
const sinon = require('sinon');
const AWS_Mock = require('aws-sdk-mock');
const AWS_SDK = require('aws-sdk');
AWSMock.setSDKInstance(AWS_SDK);
Configure stub that will be called
const stub = sinon.stub();
stub.onCall(0).returns(1);
stub.onCall(1).returns(2);
Mock Service method
Make sure that you're mocking the exact signature of the method.
AWSMock.mock('CloudFormation', 'describeStacks', function(params, cb) {
cb(null, stub());
});
Our Mocked method in action
const cf = new AWS_SDK.CloudFormation();
cf.describeStacks({}, (err, data) => {
if(err) {
console.err(err);
}
console.log(data); // 1
});
cf.describeStacks({}, (err, data) => {
if (err) {
console.err(err);
}
console.log(data); // 2
});

How to use Promise's reject?

As the documentation says
resolve is
The Promise.resolve(value) method returns a Promise object that is
resolved with the given value.
reject is
The Promise.reject(reason) method returns a Promise object that is
rejected with the given reason.
I understand the uses of resolve but what will be the uses of reject and when to use it ?
Promise.reject is promise's way of throwing errors. Usually you would have a condition inside of your promise:
const user = {
name: 'John',
age: 17
}
const p = new Promise((resolve, reject) => {
if (user.age > 18) {
resolve('Welcome!');
} else {
reject(new Error('Too young!'));
}
});
You can then chain then and catch methods to handle the results of resolve and reject respectively.
p.then(message => {
console.log(message); // 'Welcome!', if promise resolves, won't work with age of 17
})
.catch(err => {
console.error(err); // 'Too young!', because promise was rejected
});
Here are few examples this statement can be used for:
Function defined to return a Promise, however you perform some sync checks and would like to return an error:
function request(data) {
if (!data) return Promise.reject("Empty data!");
// other logic
}
Unit tests, for example you would like to test that default data is used if service returns error (rejected promise):
const mockService = mock(Service);
// mock request method to return rejected promise
when(mockService.performRequest()).thenReturn(Promise.reject("Failed!"));
// inject mock instance and check that default data used if service failed
const sut = new ClassUnderTest(mockService);
expect(sut.getData()).to.eq("Default data");

How to properly test functions that return Mongoose queries as Promises

I'm trying to write a basic unit test to work on the function below, but can't get it to work. How do I test that something like a proper npm-express response is returned?
I already looked at Using Sinon to stub chained Mongoose calls, https://codeutopia.net/blog/2016/06/10/mongoose-models-and-unit-tests-the-definitive-guide/, and Unit Test with Mongoose, but still can't figure it out. My current best guess, and the resulting error, is below the function to be tested. If possible, I don't want to use anything but Mocha, Sinon, and Chai.expect (i.e. not sinon-mongoose, chai-as-expected, etc.). Any other advice, like what else I can/should test here, is welcome. Thank you!
The function to be tested:
function testGetOneProfile(user_id, res) {
Profiles
.findOne(user_id)
.exec()
.then( (profile) => {
let name = profile.user_name,
skills = profile.skills.join('\n'),
data = { 'name': name, 'skills': skills };
return res
.status(200)
.send(data);
})
.catch( (err) => console.log('Error:', err));
}
My current best-guess unit test:
const mongoose = require('mongoose'),
sinon = require('sinon'),
chai = require('chai'),
expect = chai.expect,
Profile = require('../models/profileModel'),
foo = require('../bin/foo');
mongoose.Promise = global.Promise;
describe('testGetOneProfile', function() {
beforeEach( function() {
sinon.stub(Profile, 'findOne');
});
afterEach( function() {
Profile.findOne.restore();
});
it('should send a response', function() {
let mock_user_id = 'U5YEHNYBS';
let expectedModel = {
user_id: 'U5YEHNYBS',
user_name: 'gus',
skills: [ 'JavaScript', 'Node.js', 'Java', 'Fitness', 'Riding', 'backend']
};
let expectedResponse = {
'name': 'gus',
'skills': 'JavaScript, Node.js, Java, Fitness, Riding, backend'
};
let res = {
send: sinon.stub(),
status: sinon.stub()
};
sinon.stub(mongoose.Query.prototype, 'exec').yields(null, expectedResponse);
Profile.findOne.returns(expectedModel);
foo.testGetOneProfile(mock_user_id, res);
sinon.assert.calledWith(res.send, expectedResponse);
});
});
The test message:
1) testGetOneProfile should send a response:
TypeError: Profiles.findOne(...).exec is not a function
at Object.testGetOneProfile (bin\foo.js:187:10)
at Context.<anonymous> (test\foo.test.js:99:12)
This is a bit of a tricky scenario. The problem here is that the findOne stub in your test returns the model object - instead, it needs to return an object which contains a property exec which in turn is a promise-returning function that finally resolves into the model value... yeah, as mentioned, it's a bit tricky :)
Something like this:
const findOneResult = {
exec: sinon.stub().resolves(expectedModel)
}
Profile.findOne.returns(findOneResult);
You also need to have the status function on the response object return an object containing a send function
//if we set up the stub to return the res object
//it returns the necessary func
res.status.returns(res);
I think you shouldn't need to change anything else in the test and it might work like that. Note that you sinon 2.0 or newer for the resolves function to exist on the stub (or you can use sinon-as-promised with sinon 1.x)
This post goes into a bit more detail on how you can deal with complex objects like that:
https://codeutopia.net/blog/2016/05/23/sinon-js-quick-tip-how-to-stubmock-complex-objects-such-as-dom-objects/