jest.spyOn mock return value not returning value - unit-testing

The code I'm trying to test:
const utils = require('../utils/utils');
let imageBuffer;
try {
imageBuffer = await utils.retrieveImageFromURI(params)
console.log(imageBuffer) // comes back as undefined when I mock the utils.retreieveImageFromURI
if (!imageBuffer || imageBuffer.length < 1024) {
throw new Error(`Retrieve from uri (${params.camera.ingest.uri}) was less than 1kb in size - indicating an error`)
}
console.log(`${params.camera.camId} - Successful Ingestion from URI`);
} catch (err) {
reject({ 'Task': `Attempting to pull image from camera (${params.camera.camId}) at ${params.camera.ingest.uri}`, 'Error': err.message, 'Stack': err.stack })
return;
}
Specifically, I'm trying to mock the utils.retrieveImageFromURI function - which has API calls and other things in it.
When I try to mock the function using spyOn I am trying it like so:
describe("FUNCTION: ingestAndSave", () => {
let fakeImageBuffer = Array(1200).fill('a').join('b'); // just get a long string
console.log(fakeImageBuffer.length) //2399
let retrieveImageFromURISpy
beforeAll(() => {
retrieveImageFromURISpy = jest.spyOn(utils, 'retrieveImageFromURI').mockReturnValue(fakeImageBuffer)
})
test("Will call retrieveImageFromURI", async () => {
await ingest.ingestAndSave({camera:TEST_CONSTANTS.validCameraObject, sourceQueueURL:"httpexamplecom", receiptHandle: "1234abcd"})
expect(retrieveImageFromURISpy).toHaveBeenCalledTimes(1)
})
afterEach(() => {
jest.resetAllMocks()
})
afterAll(() => {
jest.restoreAllMocks()
})
})
When I do this, I get a console log that imageBuffer (which is supposed to be the return of the mocked function) is undefined and that, in turn, triggers the thrown Error that "Retrieve from uri ...." ... which causes my test to fail. I know I could wrap the test call in a try/catch but the very next test will be a "does not throw error" test... so this needs to be solved.
It's not clear to me why the mockReturnValue isn't getting returned.
Other steps:
I've gone to the REAL retrieveImageFromURI function and added a console log - it is not running.
I've changed mockReturnValue to mockImplementation like so:
retrieveImageFromURISpy = jest.spyOn(utils, 'retrieveImageFromURI').mockImplementation(() => {
console.log("Here")
return fakeImageBuffer
})
And it does NOT console log 'here'. I'm unsure why not.
I have also tried to return it as a resolved Promise, like so:
retrieveImageFromURISpy = jest.spyOn(utils, 'retrieveImageFromURI').mockImplementation(() => {
console.log("Here")
return Promise.resolve(fakeImageBuffer)
})
Note, this also doesn't console log.
I've also tried to return the promise directly with a mockReturnValue:
`retrieveImageFromURISpy = jest.spyOn(utils, 'retrieveImageFromURI').mockReturnValue(Promise.resolve(fakeImageBuffer)`)

Related

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 can I unit test a retryWhen operator in rxjs?

I am attempting to unit test a custom RxJS operator. The operator is very simple, it uses RetryWhen to retry a failed HTTP request, but has a delay and will only retry when the HTTP Error is in the 500 range. Using jasmine, and this is in an Angular application.
I've looked at this:
rxjs unit test with retryWhen
Unfortunately, updating the SpyOn call doesn't seem to change the returned observable on successive retries. Each time it retries it is retrying with the original spyon Value.
I have also looked at a bunch of rxjs marble examples, none of which seem to work. I am not sure it is possible to use rxjs marbles here, because (AFAIK) there is no way to simulate a situation where you first submit an errored observable, then submit a successful observable on subsequent tries.
The code is basically a clone of this:
https://blog.angularindepth.com/retry-failed-http-requests-in-angular-f5959d486294
export function delayedRetry(delayMS: number, maxRetry) {
let retries = maxRetry;
return (src: Observable<any>) =>
src.pipe(
retryWhen((errors: Observable<any>) => errors.pipe(
delay(delayMS),
mergeMap(error =>
(retries-- > 0 && error.status >= 500) ? of(error) : throwError(error))
))
);
}
I would like to be able to demonstrate that it can subscribe to an observable that returns an error on the first attempt, but then returns a successful response. The end subscription should show whatever success value the observable emits.
Thank you in advance for any insights.
try use this observable as source observable to test
const source = (called,successAt)=>{
return defer(()=>{
if(called<successAt){
called++
return throwError({status:500})
}
else return of(true)
})
}
test
this.delayedRetry(1000,3)(source(0,3)).subscribe()
To test the retry functionality, you need a observable which emits different events each time you call it. For example:
let alreadyCalled = false;
const spy = spyOn<any>(TestBed.inject(MyService), 'getObservable').and.returnValue(
new Observable((observer) => {
if (alreadyCalled) {
observer.next(message);
}
alreadyCalled = true;
observer.error('error message');
})
);
This observable will emit an error first and after that a next event.
You can check, if your observable got the message like this:
it('should retry on error', async(done) => {
let alreadyCalled = false;
const spy = spyOn<any>(TestBed.inject(MyDependencyService), 'getObservable').and.returnValue(
new Observable((observer) => {
if (alreadyCalled) {
observer.next(message);
}
alreadyCalled = true;
observer.error('error message');
})
);
const observer = {
next: (result) => {
expect(result.value).toBe(expectedResult);
done();
}
}
subscription = service.methodUnderTest(observer);
expect(spy).toHaveBeenCalledTimes(1);
}
Building on a previous answer I have been using this, which gives you more control over what's returned.
const source = (observables) => {
let count = 0;
return defer(() => {
return observables[count++];
});
};
Which can then be used like this
const obsA = source([
throwError({status: 500}),
of(1),
]);
Or it can then be used with rxjs marbles like
const obsA = source([
cold('--#', null, { status: 500 }),
cold('--(a|)', { a: 1 }),
]);

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/

How to mock DialogService.open(...).whenClosed(...) with Jasmine?

We have some TypeScript code using the Aurelia framework and Dialog plugin that we are trying to test with Jasmine, but can't work out how to do properly.
This is the source function:
openDialog(action: string) {
this._dialogService.open({ viewModel: AddAccountWizard })
.whenClosed(result => {
if (!result.wasCancelled && result.output) {
const step = this.steps.find((i) => i.action === action);
if (step) {
step.isCompleted = true;
}
}
});
}
We can create a DialogService spy, and verify the open method easily - but we can't work out how to make the spy invoke the whenClosed method with a mocked result parameter so that we can then assert that the step is completed.
This is the current Jasmine code:
it("opens a dialog when clicking on incomplete bank account", async done => {
// arrange
arrangeMemberVerificationStatus();
await component.create(bootstrap);
const vm = component.viewModel as GettingStartedCustomElement;
dialogService.open.and.callFake(() => {
return { whenClosed: () => Promise.resolve({})};
});
// act
$(".link, .-arrow")[0].click();
// assert
expect(dialogService.open).toHaveBeenCalledWith({ viewModel: AddAccountWizard });
expect(vm.steps[2].isCompleted).toBeTruthy(); // FAILS
done();
});
We've just recently updated our DialogService and ran into the same issue, so we've made this primitive mock that suited our purposes so far. It's fairly limited and doesn't do well for mocking multiple calls with different results, but should work for your above case:
export class DialogServiceMock {
shouldCancelDialog = false;
leaveDialogOpen = false;
desiredOutput = {};
open = () => {
let result = { wasCancelled: this.shouldCancelDialog, output: this.desiredOutput };
let closedPromise = this.leaveDialogOpen ? new Promise((r) => { }) : Promise.resolve(result);
let resultPromise = Promise.resolve({ closeResult: closedPromise });
resultPromise.whenClosed = (callback) => {
return this.leaveDialogOpen ? new Promise((r) => { }) : Promise.resolve(typeof callback == "function" ? callback(result) : null);
};
return resultPromise;
};
}
This mock can be configured to test various responses, when a user cancels the dialog, and scenarios where the dialog is still open.
We haven't done e2e testing yet, so I don't know of a good way to make sure you wait until the .click() call finishes so you don't have a race condition between your expect()s and the whenClosed() logic, but I think you should be able to use the mock in the test like so:
it("opens a dialog when clicking on incomplete bank account", async done => {
// arrange
arrangeMemberVerificationStatus();
await component.create(bootstrap);
const vm = component.viewModel as GettingStartedCustomElement;
let mockDialogService = new MockDialogService();
vm.dialogService = mockDialogService; //Or however you're injecting the mock into the constructor; I don't see the code where you're doing that right now.
spyOn(mockDialogService, 'open').and.callThrough();
// act
$(".link, .-arrow")[0].click();
browser.sleep(100)//I'm guessing there's a better way to verify that it's finished with e2e testing, but the point is to make sure it finishes before we assert.
// assert
expect(mockDialogService.open).toHaveBeenCalledWith({ viewModel: AddAccountWizard });
expect(vm.steps[2].isCompleted).toBeTruthy(); // FAILS
done();
});