jest toEqual not working with exception matching - node.js

I'm new to jest so I'm not sure if its my fault. So here is my test case
it('should throw error if wrong email or phone number is provided', async () => {
await expect(userService.verifyCredentials('invalidemail#invaliddomain.com', 'sayantan94'))
.rejects
.toEqual(new UnauthorizedException('Invalid email or phone number'));
})
But this fails even though the same exception is thrown. Here is the output
FAIL src/modules/user/user.service.spec.ts (5.156s)
● UserService › verifyCredentials › should throw error if wrong email or phone number is provided
expect(received).toStrictEqual(expected)
Expected value to equal:
[Error: [object Object]]
Received:
[Error: [object Object]]
Difference:
Compared values have no visual difference.
88 | await expect(userService.verifyCredentials('invalidemail#invaliddomain.com', 'sayantan94'))
89 | .rejects
> 90 | .toEqual(new UnauthorizedException('Invalid email or phone number'));
| ^
91 | })
92 | })
93 |
How do I check this?

Instead of toEqual try toThrow
it('should throw error if wrong email or phone number is provided', async () => {
await expect(userService.verifyCredentials('invalidemail#invaliddomain.com', 'sayantan94'))
.rejects
.toThrow('Invalid email or phone number');
});
Ref: https://jestjs.io/docs/en/expect.html#rejects

Related

Not able to test an event of Stencil app with Jest

I'm trying to test a click event originated from an icon (<i>) in a Stencil component. The thing is I am able to debug the test and I see that the test triggers the correct method on the component, but Jest is not able to detect\register the event and the toHaveBeenCalled() method fails.
input.spec.jsx
it("dispatches icon click event", async () => {
const page = await newSpecPage({
components: [Input], //just the component name
template: () => (<input-fields value="some input value"></input-fields>),
});
const eventSpy = jest.fn();
page.win.addEventListener('input-field-search-event', eventSpy);
await page.root.shadowRoot.querySelector('.icon-container').click();
await page.waitForChanges();
expect(eventSpy).toHaveBeenCalled();
});
input.tsx
private dispatchSearchEvent(e){
//i can stop ay this point with a break point and the data passed by the test is correct
this.tipInputEnterEvent.emit({type: "input-field-search-event", event: e, data: this.value});
}
the error
● Input tests › dispatches icon click event
expect(jest.fn()).toHaveBeenCalled()
Expected number of calls: >= 1
Received number of calls: 0
334 | await page.root.shadowRoot.querySelector('.icon-container').click();
335 | await page.waitForChanges();
> 336 | expect(eventSpy).toHaveBeenCalled();
| ^
337 | });
338 |
339 | });
By default the name of custom events is the property name, in your case tipInputEnterEvent. So the listener should instead be:
page.win.addEventListener('tipInputEnterEvent', eventSpy);
You can also change the event name if you want:
#Event({ eventName: 'input-field-search-event' }) tipInputEnterEvent: EventEmitter;

How can i deal with JEST handles potentially keeping Jest from exiting when using node-ftech

I have written all my tests and i am receiving the below error on node-fetch when i test although all my tests pass
Jest has detected the following 5 open handles potentially keeping Jest from exiting:
11 | describe('the vip rotating ips', () => {
12 | test('the VIP json url exists', async () => {
> 13 | await nodeFetch('https://go-vip.net/ip-ranges.json')
| ^
14 | .then((r) => {
15 | expect(r.status).toBe(200);
16 | })
The whole test is
test('the VIP json url exists', async () => {
await nodeFetch('https://go-vip.net/ip-ranges.json')
.then((r) => {
expect(r.status).toBe(200);
})
.catch((e) => {
fail(e);
});
});
Ive sought out numerous stackoverlflow solutions and documents however none seem to either give me a full explanation of the issue not a solution that suits my case.
I believe it's something related to timeouts
Can someone explain to me the problem and help me deliver a solution.

I am getting issue in mongoose calback in custom validation

I am using express-validator and have made one custom validation with mongoose database find, now I can't send withMessage. Here is my code althought process stop at Error but it does not show message in Json, but when user created it is show all.
body('mobile', 'Mobile number is required')
.custom((value, {req, loc, path}) => {
User.countDocuments({mobile: value}, function (err, c) {
if (err) {
console.log('An error happen in db')
}
if (c > 0) {
throw new Error('Mobile already exists finally')
} else {
return value
}
})
return value
})
.withMessage('Mobile already exists'),
Following is log of console
functions: Beginning execution of "app"
> events.js:298
> throw er; // Unhandled 'error' event
> ^
>
> Error: Mobile already exists finally
> at /Users/kamal/Documents/personal/asghar/codes/functions/app/controllers/users_controller.js:117:23
> at /Users/kamal/Documents/personal/asghar/codes/functions/node_modules/mongoose/lib/model.js:4849:16
> at /Users/kamal/Documents/personal/asghar/codes/functions/node_modules/mongoose/lib/model.js:4849:16
> at /Users/kamal/Documents/personal/asghar/codes/functions/node_modules/mongoose/lib/helpers/promiseOrCallback.js:24:16
> at /Users/kamal/Documents/personal/asghar/codes/functions/node_modules/mongoose/lib/model.js:4872:21
> at /Users/kamal/Documents/personal/asghar/codes/functions/node_modules/mongoose/lib/query.js:4379:11
> at /Users/kamal/Documents/personal/asghar/codes/functions/node_modules/kareem/index.js:135:16
> at processTicksAndRejections (internal/process/task_queues.js:79:11)
> Emitted 'error' event on Function instance at:
> at /Users/kamal/Documents/personal/asghar/codes/functions/node_modules/mongoose/lib/model.js:4851:13
> at /Users/kamal/Documents/personal/asghar/codes/functions/node_modules/mongoose/lib/helpers/promiseOrCallback.js:24:16
> [... lines matching original stack trace ...]
I need to add if condition near return value but problem is call back does not bring me value of c here, in above it is comign correct and even stop due to Error raising, but I don't want to raise error instead want to go further withMessage('Mobile already exists') I am sure doing mistake in callback. Please suggest solution
This should work
body("mobile").custom(value => {
return User.countDocuments({ mobile: value })
.then(count => {
if (count > 0) return Promise.reject("Mobile number already exists");
})
.catch(error => console.error(error.message));
});
From express-validator
documentation
Custom validators may return Promises to indicate an async validation
(which will be awaited upon), or throw any value/reject a promise to
use a custom error message. Note: if your custom validator returns a
promise, it must reject to indicate that the field is invalid.
After playing and seeing many many documentation I solved this by doing following, in my User model I put following code, as my model is actualy mongoose module.
UserSchema.statics = {
isValidMobile(mobile) {
console.log(` Searching obile ${mobile} number `)
return this.findOne({mobile: mobile}).then((result) => {
if (result) throw new Error('Mobile Number already exists')
})
},
}
Now in my validation I put following line instead of above lines.
body('mobile', 'Mobile number is required')
.custom((val) => User.isValidMobile(val))
It worked, and I am getting proper messages with whole JSON, as it seem custom require proper true/false reply so my method is replying just true or false and it is working fine with correct message, but the message is being used came from User model class not from validation, but that works. Thank you for responses.

How to verify results of spied function (callThrough)

I want to verify/assert the results of spied function. I'm using nestjs framework with jasmine. I create a jasmine spy on a method i want to "spy" on, that is, eavesdrop args and response/exception. However, I can't access return value of spied method.
Let's say I have an emitter and listener and I want to assert that my listener throws an exception when a DB operation fails.
Listener:
onModuleInit() {
this.emitter.on('documentDeleted', d => this.onDocumentDeleted(d));
}
#CatchAndLogAnyException()
private async onDocumentDeleted(dto: DocumentDeletedEventDTO) {
this.logger.log(`Deleting document with id '${dto.id}'...`);
const result = await this.ResearchHearingTestModel.deleteOne({ _id: dto.id });
if (!result.ok) {
throw new DataAccessException(
`Deleting document with id '${dto.id}' failed. Model.deleteOne(id) result: ${result}`,
);
}
if (result.n < 1) {
throw new DocumentNotFoundException(`Deleting document with id '${dto.id}' failed.`);
}
this.logger.log(`Deleted document with id '${dto.id}.`);
}
Test:
const mockId = 123;
const spyDelete = spyOn(model, 'deleteOne').and.returnValue({ ok: 1, n: 0 });
const spyOnDeleted = spyOn(listener, 'onDocumentDeleted');
spyOnDeleted.and.callThrough();
await emitter.emit('documentDeleted', new DocumentDeletedEventDTO(mockId));
expect(spyOnDeleted).toHaveBeenCalledTimes(1);
expect(spyDelete).toHaveBeenCalledTimes(1);
expect(spyDelete).toHaveBeenCalledWith(expect.objectContaining({ _id: mockId }));
expect(spyOnDeleted).toThrow(DocumentNotFoundException);
So when debugging, I can see spyOnDeleted["[[Scopes]]"][0].spy.calls.mostRecent["[[Scopes]]"][0].calls[0].returnValue is a promise i'm probably looking for, but I can't access it or verify on it.
And when I run the test, this is the output:
expect(received).toThrow(expected)
Expected name: "DocumentNotFoundException"
Received function did not throw
95 | expect(spyDelete).toHaveBeenCalledTimes(1);
96 | expect(spyDelete).toHaveBeenCalledWith(expect.objectContaining({ _id: mockId }));
> 97 | expect(spyOnDeleted).toThrow(DocumentNotFoundException);
| ^
98 | });
99 | });
100 | });
I've seen CallThrough injected spy and several other questions that are similar, but I'm still hoping it's possible to spy on callThrough methods and eavesdrop on in/out of it. Any suggestions?
toThrow cannot be used on spies. You can use spies to mock behavior or use the actual behavior with callThrough and then make sure the method was called with specific parameters. But a spy will not have information about the result it produced (value or error) so you cannot set expectations on it.
If you want to test the behavior of onDocumentDeleted you have to either test it indirectly by observing the effects of the method. In your case (with #CatchAndLogAnyException), it seems to write to the log!? So you can spy on the log and expect it to be called with the error message. Or alternatively, you test the method directly by making it public.

Sinon spy fails with bad arguments for one function but succeeds for another identical one

I am really confused by the behaviour I'm seeing. My spy keeps misreporting the arguments. If I create identical functions with the same signature and arguments and spy on that separately then the copy works fine. I cannot work out what's going on!
So, here's the code:
NB: alert is the original function, test is the new one I created to check what's going on.
# index.js
class Alerter {
getOnErrorFn(name) {
return (error) => {
...
alert = {
opts: {
tags: 'Unknown Error'
}
}
...
if (alert) {
this.test(name, error.message, Object.assign({}, alert.opts, {smc_error: error.toLog()}), alert.level);
this.alert(name, error.message, Object.assign({}, alert.opts, {smc_error: error.toLog()}), alert.level); }
};
}
test(serviceName, message, opts={}, level=this.CRITICAL) {
console.log(serviceName, message, opts, level);
}
alert(serviceName, message, opts={}, level=this.CRITICAL) {
console.log(serviceName, message, opts, level);
...
}
Here's my test code (All other tests are commented out and this is the only file in the test suite);
# alerter.spec.js
const sandbox = sinon.sandbox.create();
describe('Alerter', function(){
let alerter;
let name = 'test-service';
let message = 'test message';
beforeEach(() => {
alerter = new Alerter(name);
});
afterEach(() => {
sandbox.restore();
});
describe('getOnErrorFn()', () => {
it.only('handles non-SMCError errors and assumes they should be alerted', () => {
const spy = sandbox.spy(alerter, 'test');
const spi = sandbox.spy(alerter, 'alert');
const onError = alerter.getOnErrorFn(name);
const error = new Error();
const smcError = SMCError.from(error);
onError(error);
expect(spy).to.have.been.calledWith(name, smcError.message, {smc_error: smcError.toLog(), tags: 'Unknown Error'}, undefined);
expect(spi).to.have.been.calledWith(name, smcError.message, {smc_error: smcError.toLog(), tags: 'Unknown Error'}, undefined);
});
});
});
And here's the result of the test run.... which is doing my absolute nut in!
$ npm test
> smc-alerting#2.1.3 test /Users/al/Studio/Projects/smc/app/smc-alerting
> mocha test/**/*.spec.js
Alerter
getOnErrorFn()
TEST test-service Caught Error { tags: 'Unknown Error',
smc_error: 'Error Caught Error\n caused by: Caught Error' } critical
ALERT test-service Caught Error { tags: 'Unknown Error',
smc_error: 'Error Caught Error\n caused by: Caught Error' } critical
1) handles non-SMCError errors and assumes they should be alerted
0 passing (34ms)
1 failing
1) Alerter getOnErrorFn() handles non-SMCError errors and assumes they should be alerted:
expected alert to have been called with arguments test-service, Caught Error, {
smc_error: "Error Caught Error
caused by: Caught Error",
tags: "Unknown Error"
}, undefined
alert(test-service, Caught Error, { smc_error: "Error Caught Error
caused by: Caught Error" }, undefined) at /Users/al/Studio/Projects/smc/app/smc-alerting/src/index.js:46:14
AssertionError: expected alert to have been called with arguments test-service, Caught Error, {
smc_error: "Error Caught Error
caused by: Caught Error",
tags: "Unknown Error"
}, undefined
alert(test-service, Caught Error, { smc_error: "Error Caught Error
caused by: Caught Error" }, undefined) at src/index.js:46:14
at Context.it.only (test/index.spec.js:173:32)
npm ERR! Test failed. See above for more details.
So, note that both console.log statements print identical results. BUT, the spy for the alert function fails with a printout that indicates that the function was called with the tags property missing from the third argument. WTF!
Or is there something I don't know happening here?
All help much appreciated. Thanks in advance!
Darnit. Got the answer. Long story short: use immutable objects!
alert() had a delete opts.tags line later down the code which of course changed the original object by the time sinon got round to checking it.

Resources