Jasmine: How to expect promise handler to not throw exception - javascript

I have this function:
reload() {
myService.queryData()
.done(...)
.always(() => throw "fake exception"); //just to simulate the failure
}
I want my test reload function and make sure it does not throw exception nor the promise callback does.
describe("reload", function () {
it("does not throw exception", function (done) {
spyOn(myService, "queryData").and.callFake(() => {
let deffered = $.deffered();
setTimeOut(() => deffered.reject(), 0)
return deffered.promise();
});
reload();
setTimeout(() => {
//this is evaluated after the exception has been thrown, but
//how to check whether exception has been thrown
}, 2);
});
});
EDIT: I might not be able to return a promise in some cases, where the return type of the function is already defined, e.g component's lifecycle event:
MyComponent extends React.Component {
componentDidMount() {
this.load(
galleryService.nodes().then(galleryResult => this.setState({ nodes: galleryResult.nodes }))
);
this.load(
galleryService.caches().then(cachesResult => this.setState({ caches: cachesResult.caches }))
);
}
}
var myComponent = React.createElement(MyComponent);
TestUtils.renderIntoDocument(myComponent); //this triggers the componentDidMount event and I need to make sure it won't throw error.

Let reload return the promise it creates. In your test case, attach a catch handler to it which triggers a test failure:
reload().catch(err => done.fail(err));
Update after question was edited: If you cannot change the return value of your original function then factor out the relevant parts into separate functions. For example:
function reloadNodes() {
return somePromise();
}
function reloadCaches() {
return anotherPromise();
}
function reload() {
reloadNodes();
reloadCaches();
}
You can then test reloadNodes and reloadCaches instead of reload. Obviously you don't need to create a separate function for each promise, instead combine your promises using something like Promise.all where appropriate.

I believe that spying on window.onerror is the way to go:
describe("reload", function () {
it("does not throw an exception", function (done) {
spyOn(window, 'onerror').and.callFake((error: any, e: any) => {
fail(error);
});
spyOn(myService, "queryData").and.callFake(() => {
let deffered = $.deffered();
setTimeout(deffered.reject, 0);
return deffered.promise();
});
});
setTimeout(done, 2);
});
});

Related

How to validate catch block of a function in Jest?

I have a basic stringify function that looks like this ->
export const stringify = <T>(value: T) => {
try {
return JSON.stringify(value);
} catch(error){
return ''
}
}
I want to write a test that can cover the catch block of the function.
I've tried adding such a test ->
it('should be able to check for errors', async () => {
await expect(stringify('')).rejects.toThrow()
})
But this test keeps throwing errors about the function not being a promise. The function isn't going into the catch block at all.
The main function isn't a promise so I can't use the promise functions of jest.
How do I test the catch block?
There is no need to use async/await in this test. Also when there is an error you are returning '' from catch block, meaning your function will not throw anything.
Something like will work for your case
it('should be able to check for errors', () => {
expect(stringify(<error value>)).toBe('')
})
Expect the function definition to Throw
const functionDef = () => {
throw new TypeError("Error Message");
};
test("Test description", () => {
expect(functionDef).toThrow(TypeError);
expect(functionDef).toThrow("Error Message");
});

Declare an Async function

I'm new in React and I'm trying to call an async function, but I get the Unexpected reserved word 'await'. ERROR
So my async function is in a Helper class who is supposed to do all the API call, and I'm calling this Helper class in my Game function
Here is my code from Class Helper :
class Helper {
constructor() {
this.state = {
movieAnswer: {},
profile_path:"",
poster_path:"",
myInit: { method: "GET", mode: "cors" }
}
}
fetchMovieFunction = async (randomMovie) => {
return new Promise((resolve) => {
fetch(`${MOVIE_KEY}${randomMovie}?api_key=${API_KEY}`, this.myInit)
.then(error => resolve({ error }))
.then(response => resolve({ poster_path: response.poster_path }));
});
}
and here is where I call fetchMovieFunction in my game function :
const helper = new Helper();
function Game() {
useEffect(() => {
setRandomMovie(Math.floor(Math.random() * (500 - 1) + 1));
const { poster_path, error } = await helper.fetchMovieFunction(randomMovie)
if (error)
return console.log({error});
setApiMovieResponse({poster_path})
}, [page]);
return ();
}
And I don't understand why on this line const { poster_path, error } = await helper.fetchMovieFunction(randomMovie) I get the Unexpected reserved word 'await'. Error like my function is not declare as an async function
two issues:
You should use await inside an async function
But the callback passed to useEffect should not be async. So, you can create an IIFE and make it async:
useEffect(() => {
(async () => {
setRandomMovie(Math.floor(Math.random() * (500 - 1) + 1));
const { poster_path, error } = await helper.fetchMovieFunction(randomMovie);
if (error) return console.log({ error });
setApiMovieResponse({ poster_path });
})();
}, [page]);
The callback itself shouldn't be async because that would make the callback return a promise and React expects a non-async function that is typically used to do some cleanup.
You will necessarily need to wrap the hook callback logic into an async function and invoke it. React hook callbacks cannot be asynchronous, but they can call asynchronous functions.
Also, since React state updates are asynchronously processed, you can't set the randomMovie value and expect to use it on the next line in the same callback scope. Split this out into a separate useEffect to set the randomMovie state when the page dependency updates, and use the randomMovie as the dependency for the main effect.
useEffect(() => {
setRandomMovie(Math.floor(Math.random() * (500 - 1) + 1));
}, [page]);
useEffect(() => {
const getMovies = async () => { // <-- declare async
const { poster_path, error } = await helper.fetchMovieFunction(randomMovie);
if (error) {
console.log({error});
} else {
setApiMovieResponse({poster_path});
}
};
if (randomMovie) {
getMovies(); // <-- invoke
}
}, [randomMovie]);
FYI
fetch already returns a Promise, so wrapping it in a Promise is superfluous. Since you are returning the Promise chain there is also nothing to await. You can return the fetch (and Promise chain).
fetchMovieFunction = (randomMovie) => {
return fetch(`${MOVIE_KEY}${randomMovie}?api_key=${API_KEY}`, this.myInit)
.then(response => return {
poster_path: response.poster_path
})
.catch(error => return { error });
}
In order to do so, It's not suggested to asynchronize a function as a callback function being used in useEffect() since Effect callbacks are synchronous to prevent race conditions.
the better approach would be inside the useEffect callback
check this out:
useEffect(() => {
async function [name] () {
.....
.....
}
[name]();
},string[])
fetchMovieFunction doesn't need to be async because you're already returning a promise. Instead just return the fetch (also a promise), and catch any errors from that call in that method:
fetchMovieFunction = (randomMovie) => {
try {
return fetch(`${MOVIE_KEY}${randomMovie}?api_key=${API_KEY}`, this.myInit)
} catch(err) {
console.log(err);
}
});
But your useEffect, or rather the function you should be calling within the useEffect, does need to be async (because you're using await, but now you can just pick up the data from the fetch when it resolves.
useEffect(() => {
async function getData() {
setRandomMovie(Math.floor(Math.random() * (500 - 1) + 1));
const { poster_path } = await helper.fetchMovieFunction(randomMovie);
setApiMovieResponse({ poster_path });
}
getData();
}, [page]);

Jest mocks and error handling - Jest test skips the "catch" of my function

I'm creating a jest test to test if metrics were logged for the error handling of the superFetch function. My approach is creating a mock function for retryFetch and returning a Promise reject event. I expect that to go to the superFetch catch but it keeps ending up in superFetch then. What can I do to handle my errors in superFetch catch?
These are the functions:
// file: fetches.js
export function retryFetch(url) {
return new Promise((resolve, reject) => {
fetch(url).then(response => {
if (response.ok) {
resolve(response);
return;
}
throw new Error();
}).catch(error => {
createSomething(error).then(createSomething => {
reject(createSomething);
});
return;
});
});
});
export function superFetch(url, name, page) {
return retryFetch(url)
.then(response => {
return response;
}).catch(error => {
Metrics.logErrorMetric(name, page);
throw error;
});
}
My jest test:
import * as fetch from '../../src/utils/fetches';
describe('Fetch fails', () => {
beforeEach(() => {
fetch.retryFetch = jest.fn(() => Promise.reject(new Error('Error')));
});
it('error metric is logged', () => {
return fetch.superFetch('url', 'metric', 'page').then(data => {
expect(data).toEqual(null);
// received data is {"ok": true};
// why is it even going here? im expecting it to go skip this and go to catch
}).catch(error => {
// this is completely skipped. but I'm expecting this to catch an error
// received error is null, metric was not called
expect(Metrics.logErrorMetric).toHaveBeenCalled();
expect(error).toEqual('Error');
});
});
});
The problem is that you overwrite the function in the exported module but superFetch use the original one inside of the module, so the overwrite will have no effect.
You could mock fetch directly like this:
global.fetch = jest.mock(()=> Promise.reject())

Unit testing logic inside promise callback

I have an ES6 / Aurelia app that I am using jasmine to test. The method I am trying to test looks something like this:
update() {
let vm = this;
vm.getData()
.then((response) => {
vm.processData(response);
});
}
Where this.getData is a function that returns a promise.
My spec file looks something like this:
describe('my service update function', () => {
it('it will call the other functions', () => {
myService = new MyService();
spyOn(myService, 'getData').and.callFake(function() {
return new Promise((resolve) => { resolve(); });
});
spyOn(myService, 'processData').and.callFake(function() { return; });
myService.update();
// this one passes
expect(myService.getData).toHaveBeenCalled();
// this one fails
expect(myService.processData).toHaveBeenCalled();
});
});
I understand why this fails - promises are asynchronous and it hasn't been resolved by the time it hits the expect.
How can I push the promises to resolve from my test so that I can test the code inside the call back?
jsfiddle of failed test: http://jsfiddle.net/yammerade/2aap5u37/6/
I got a workaround running by returning an object that behaves like a promise instead of an actual promise
describe('my service update function', () => {
it('it will call the other functions', () => {
myService = new MyService();
spyOn(myService, 'getData').and.returnValue({
then(callback) {
callback();
}
});
spyOn(myService, 'processData').and.callFake(function() { return; });
myService.update();
// this one passes
expect(myService.getData).toHaveBeenCalled();
// this one fails
expect(myService.processData).toHaveBeenCalled();
});
});
Fiddle here: http://jsfiddle.net/yammerade/9rLrzszm/2/
Is there anything wrong with doing it this way?
it((done) => {
// call done, when you are done
spyOn(myService, 'processData').and.callFake(function() {
expect(myService.processData).toHaveBeenCalled();
done();
});
})

How to mock readline.on('SIGINT')?

I have this piece of code:
function getMsg() {
return new Promise(function (resolve, reject) {
var input = [];
var rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
rl.on('line', function (cmd) {
if (cmd.trim()) {
input.push(cmd);
} else {
rl.close();
}
});
rl.on('close', function () {
rl.close();
resolve(input.join('\n'));
});
rl.on('SIGINT', reject);
});
}
I'm trying to test this function, my attempt, so far, is this:
it('should reject if SIGINT is sent', function () {
sandbox.stub(readline, 'createInterface', function () {
return {
on: function (action, callback) {
callback();
},
prompt: function () {},
close: function () {}
};
});
return getMsg().then(null).catch(function () {
expect(true).to.be.equal(true);
});
});
But of course, that doesn't simulate a SIGINT, how do I do this?
I think you need a different setup:
const EventEmitter = require('events').EventEmitter
...
it('should reject if SIGINT is sent', function () {
let emitter = new EventEmitter();
sandbox.stub(readline, 'createInterface', function () {
emitter.close = () => {};
return emitter;
});
let promise = getMsg().then(function() {
throw Error('should not have resolved');
}, function (err) {
expect(true).to.be.equal(true);
});
emitter.emit('SIGINT');
return promise;
});
The object returned by readline.createInterface() inherits from EventEmitter, so that's what the stub will return. The additional close function is simply added to it to prevent errors when it gets called.
You can't return the promise returned by getMsg directly, because that doesn't give you a chance to emit the SIGINT "signal" (really just an event, but for testing purposes it'll work just fine). So the promise is stored.
The test should fail with the "fulfilled" handler gets called, which had to be done explicitly, otherwise Mocha thinks the test passed.
Next, the SIGINT is sent (which should trigger the rejection handler as intended), and the promise is returned.

Categories

Resources