Jest: Testing a nested promise - javascript

I'm new to jest, and having trouble determining how to test results nested inside promises. Specifically:
myMethod: function (e) {
let self = this
self.resetErrors()
Parser.parseFile(this.form.uploadFile).then(res => {
const hasErrors = self.validFile(res)
if (!hasErrors) {
self.processFile(res)
}
})
}
I'd like to test to ensure that, assuming hasErrors is false, self.processFile fires. Here's my current (failing) best effort:
describe("if the provided data is valid", () => {
it('runs processFile', () => {
const mockProcessFile = jest.fn()
mockParser = jest.fn(() => {
new Promise((resolve, reject) => {
return ValidMockData
}).then((loanData) => {
expect(mockProcessFile).toBeCalled()
})
})
CsvParser.parseFile = mockParser
wrapper.vm.validFile = jest.fn(true)
wrapper.vm.processFile = mockProcessFile
wrapper.vm.store().resolve((data) => {
expect(mockProcessFile).toBeCalled()
})
})
})
At present I'm getting a Cannot read property 'then' of undefined error - which makes sense, but I'm not sure how exactly I'm supposed to crack into expectations inside of a then() call. Any thoughts appreciated

You need to store the Promise you create in the test so you can use await to let the test finishing after it was resolved:
describe("if the provided data is valid", async() => {
it('runs processFile', () => {
const mockProcessFile = jest.fn()
const p = Promise.resolve(ValidMockData)
CsvParser.parseFile = jest.fn(() => p)
wrapper.vm.validFile = jest.fn(true)
wrapper.vm.processFile = mockProcessFile
wrapper.vm.store()
await p
expect(mockProcessFile).toBeCalled()
expect(mockProcessFile).toBeCalled()
})
})

Related

What is the usage of this code in CancelToken.js for the source code of Axios

Recently I was learning the source code of Axios(https://github.com/axios/axios).
However, in CancelToken.js, there is a part of code that I don't understand.
// eslint-disable-next-line func-names
this.promise.then = onfulfilled => {
let _resolve;
// eslint-disable-next-line func-names
const promise = new Promise(resolve => {
token.subscribe(resolve);
_resolve = resolve;
}).then(onfulfilled);
promise.cancel = function reject() {
token.unsubscribe(_resolve);
};
return promise;
};
Originally, I think it is used to overwrite the then method of this.promise. And then, I write a demo to verify my idea.
let resolve
let p = new Promise((res, rej) => resolve = res)
p.then(val => {
console.log('In p.then ', val)
})
p.then = onFulfill => {
console.log('I am here')
const promise = new Promise(resolve => {
resolve(2)
}).then(onFulfill)
return promise
}
console.log(p.then)
resolve(1)
If as what I expected, 'I am here' should be print. However, it can't print 'I am here'. So I don't understand the usage of this.promise.then = onFulfilled => ... in Axios source code.
Can anyone help me! THX!
Overwriting the method
You are correct, this does overwrite the then method of this Promise instance; the important thing here is when that happens.
If you consider this (extremely poor) resolve-only Promise implementation:
class Future {
constructor(executor) {
this._state = 'pending';
executor(result => {
if (this._state !== 'pending') {
return;
}
this._state = 'fulfilled';
if (!this._onFulfilled) {
return;
}
this._onFulfilled(result);
});
}
then(onFulfilled) {
this._onFulfilled = onFulfilled;
}
}
you can see that, if called like so:
const future = new Future(resolve => setTimeout(resolve, 1000));
future.then(() => console.log('fulfilled'));
future.then = console.log;
future.then('and then?');
reassigning then will leave the internal value of _onFulfilled untouched, and any subsequent calls to future.then will use the modified method.
In Axios?
In the Axios code, this appears to be used to allow using the CancelToken instance in two ways:
as an object owning a thenable, where the modified then appends the onFufilled handler to the internal _listeners array and returns a Promise, or
as a rough Observable, pushing the handler directly onto the _listeners array
So, mocking this up using the following setup:
let resolve;
const future = new Promise(res => resolve = res)
const listeners = [];
future.then(val => {
for (const listener of listeners) {
listener(val);
}
});
future.then = onFulfilled => new Promise(resolve => {
listeners.push(resolve);
}).then(onFulfilled);
the output of this:
for (let index = 0; index < 10; index++) {
future.then(val => console.log(`${index}: ${val}`));
}
is equivalent to that of this:
for (let index = 0; index < 10; index++) {
listeners.push(val => console.log(`${index}: ${val}`));
}
when kicked off with:
resolve(1);

What is this function doing in JavaScript?

I'm learning JavaScript and I'm learning about Promises. I'm having a hard time to understand the following code:
const button = document.querySelector("button");
const div = document.querySelector("div");
const setText = (text) => {
div.textContent = text
}
const checkAuth = () => {
return new Promise((resolve, reject) => {
setText('Checking Auth...')
setTimeout(() => {
resolve(true);
}, 2000);
});
};
const fetchUser = () => {
return new Promise((resolve, reject) => {
setText('Fetching User...');
setTimeout(() => {
resolve({ name: "Max" });
}, 2000);
});
};
button.addEventListener("click", () => {
checkAuth()
.then(
isAuth => {
if (isAuth) {
return fetchUser()
}
}
)
.then(
user => {
setText(user.name)
}
)
});
I understand the first two functions checkAuth and fetchUser, they return a resolved Promise after 2 seconds.
I can't really understand how does the event listener of button work. I see it calls checkAuth() and when executed, the div text of the HTML document changes to Checking Auth... for two seconds (until the function resolves successfully the Promise. When resolved, it enters into the .then() part of the code.
I'm struggling to understand where does isAuth and user come from. I feel like they are some implicit declaration of a function (called inside themselves?), it's looking really odd to me.
checkAuth() returns a promise and the value with which it resolves is passed to the callback function of checkAuth().then(...) which is then assigned to isAuth parameter.
In the case of user, it is the value with which the promise returned by checkAuth().then() is resolved. If the callback function of checkAuth().then(...) returns a non-promise value, user will be that non-promise value.
If the callback function of checkAuth().then() returns a promise, then user will be the value with which that promise, returned by the callback function, resolves.
Here's a simplified example that might help you understand how isAuth and user are assigned a value:
Promise.resolve(true)
.then(isAuth => {
console.log("isAuth: " + isAuth);
return { name: "John Doe" };
})
.then(user => console.log(user));
Here is the explanation:
const button = document.querySelector("button");
const div = document.querySelector("div");
const setText = (text) => {
div.textContent = text
}
const checkAuth = () => {
return new Promise((resolve, reject) => {
setText('Checking Auth...')
setTimeout(() => {
resolve(true); // The return value when the promise resolves which is in this case is `true`;
}, 2000);
});
};
const fetchUser = () => {
return new Promise((resolve, reject) => {
setText('Fetching User...');
setTimeout(() => {
resolve({ name: "Max" }); // Here when the promise resolves it is returning `{ name: "Max" }`;
}, 2000);
});
};
button.addEventListener("click", () => {
checkAuth()
.then(isAuth => { // This is an arrow function, you can see we are accepting the `isAuth` parameter;
if (isAuth) { // Regular if...
return fetchUser() // And the this also returns an promise
}
}
)
.then(user => { // Which is resolved here, And the user is the `{ name: "Max" }`;
setText(user.name)
}
)
});
isAuth is returned by checkAuth and user is returned by fetchUser.
I'll re-write this in async/await format. You can compare to make better sense of it.
const delay = (time) => new Promise(res => setTimeout(res(), time));
const checkAuth = async () => {
setText('Checking Auth...');
await delay(2000);
}
const fetchUser = async () => {
setText('Fetching User...');
await delay(2000);
}
const executeOrder = async () => {
const isAuth = await checkAuth();
if (isAuth) {
const user = await fetchUser();
setText(user.name)
}
}
button.addEventListener("click", executeOrder);

Locked it method in chai

I have a js file which supplies some db operations. This file works with promises only which can be chained. To test that class I work with an async function.
The problem is, that whenever I work with promises inside my test function the it function gets blocked for every other test later.
Here are two examples:
'use strict'
const exec = require('child_process').exec
const path = require('path')
const request = require('request')
const expect = require('chai').expect
const createTableStatements = require('../data')
test()
async function test () {
await testGetUser()
console.log('1')
await testGetFaculties()
}
function testGetUser () {
return new Promise((resolve1) => {
describe('test get user', function () {
const db = require('../dbInterface')
it('test get user should be complete', function () {
db.dbFunctions.dropAll()
.then(onResolve => {
return db.dbFunctions.createTable(createTableStatements.createTableStatements.user)
}
)
.then(() => {
console.log('success create user table')
return db.dbFunctions.addUser('1', 'firstName', 'lastName', 'email')
})
.then(resolve => {
return db.dbFunctions.getUser('email', undefined)
})
.then(result => {
expect(result.toString().includes('dummy')).to.equal(false)
})
.then(resolve => {
return db.dbFunctions.dropAll()
})
.then(resolve => {
console.log('resolve')
resolve1()
})
.catch(err => console.log(err))
})
})
})
}
function testGetFaculties () {
return new Promise(resolve => {
describe('test get faculties', function () {
let db
before(function () {
db = require('../dbInterface')
})
console.log('displayed')
it('should work', function () {
console.log('locked')
expect(db.dbFunctions.getFaculties('hsa')).to.be.an('array').that.does.include('Science')
resolve()
})
})
})
}
And this is the output
resolve
1
displayed
As you can see console.log('locked') is not being processed.
What i figured out so far, that I only have this issue when I call expect within a then function. But this is necessary for my tests.
The test () function should contain much more tests, only for this question I shortened it.
And for clarification: If I only test methods type of testGetFaculties () which don't contains another promise chain inside it works like it should.
Any idea why this is like it is?
Most probably the console.log( 'locked' ); doesn't do anything, because your previous test case was not finished at all.
Writing describe, it, before inside a Promise and containing unreturned Promises is something that you should not do.
Much better test case would look like :
'use strict'
const exec = require('child_process').exec
const path = require('path')
const request = require('request')
const expect = require('chai').expect
const createTableStatements = require('../data')
// You use this in both test cases anyway
const db = require('../dbInterface');
describe('test get user', function () {
it('test get user should be complete', function () {
return db
// ^ returning promise will make sure that the test ends when the promise ends.
.dbFunctions
.dropAll()
.then(onResolve => { ... } )
...
)
} );
} );
describe('test get faculties', function () {
it('should work', function () {
return db
// ^ returning promise will make sure that the test ends when the promise ends.
.dbFunctions
.getFaculties('hsa')
.then( value => {
// ^ You actually need to test the value of the resolve promise
expect( value ).to.be.an('array').that.does.include('Science');
} )
} );
} );

Jest/enzyme: How to test for .then and .catch of an nested asynchronous function

I'm trying to do some enzyme/jest unit testing for a asynchronous function in my reactJS component, which gets injected as prop.
My Problem is to test for a value in the then() part of the function and to test for catch() if an error occures.
This is how the function of my component (<CreateAccount />) looks like:
_onSubmit = (event) => {
event.preventDefault()
const { username, password } = this.state
this.props.createUserMutation({
variables: { username, password }
}).then(response => {
const token = response.data.createUser.token
if (token) {
Cookies.set('auth-token', token, { expires: 1 })
}
}).catch(error => {
console.warn(error)
})
}
The first test should check for .catch(error => {}) as data is undefined:
it('_onSubmit() should throw error if data is missing', () => {
const createUserMutation = () => {
return Promise.resolve({})
}
const wrapper = shallow(<CreateAccount createUserMutation={createUserMutation} />)
wrapper.update().find(Form).simulate('submit', {
preventDefault: () => {}
})
const state = wrapper.instance().state
expect(wrapper).toThrow() // <-- How to do it correctly?
})
And the second test should check if cookie is set correctly. Here I don't know how to do that? I think I have to mock Cookie
it('_onSubmit() should get token', () => {
const createUserMutation = () => {
return Promise.resolve({
data: {
createUser: { token: 'token' }
}
})
}
const wrapper = shallow(<CreateAccount createUserMutation={createUserMutation} />)
wrapper.find(Form).simulate('submit', {
preventDefault: () => {}
})
// How to check for token value and Cookies?
})
What I usually have to do when I want to see if the spy worked on the catch or then, is to add another then() on the test. For example:
it('_onSubmit() should throw error if data is missing', () => {
const createUserMutation = jest.fn(() => Promise.reject(new Error()));
const spy = jest.spyOn(console,"warn");
const wrapper = shallow(<CreateAccount createUserMutation={createUserMutation} />)
wrapper.update().find(Form).simulate('submit', {
preventDefault: () => {}
});
return createUserMutation.catch(() => {
expect(wrapper).toMatchSnapshot();
})
.then(() => {
expect(spy).toHaveBeenCalledTimes(1);
});
})
I guess it is somehow related to how NodeJS handles the queues, promises, ticks, etc, internally.
That is the rejected/catch branch. If you want to test the IF path, just use a Promise.resolve and return a promise.then() instead of catch.
Why are you using console.warn for an error? Use console.error instead. You will need to mock it out to a spy as well to test it.
First test:
it('_onSubmit() should throw error if data is missing', (done) => {
const createUserMutation = () => new Promise();
const wrapper = shallow(<CreateAccount createUserMutation={createUserMutation} />)
wrapper.update().find(Form).simulate('submit', {
preventDefault: () => {}
})
const state = wrapper.instance().state
createUserMutation.resolve().then(() => {
expect(console.warn).toHaveBeenCalled();
done();
});
})
If you are running this in a mock browser environment and not a real browser then you must mock out Cookies.set.
Second test:
it('_onSubmit() should get token', (done) => {
const createUserMutation = () => new Promise();
const wrapper = shallow(<CreateAccount createUserMutation={createUserMutation} />)
wrapper.find(Form).simulate('submit', {
preventDefault: () => {}
});
jest.spyOn(window.Cookies, 'set');
const response = {
data: {
createUser: { token: 'token' }
}
}
createUserMutation.resolve(response).then(() => {
expect(window.Cookies.set).toHaveBeenCalled();
done();
});
})
afterEach(() => {
// Reset the spies so that they don't leak to other tests
jest.restoreAllMocks();
});

Is it safe to resolve a promise multiple times?

I have an i18n service in my application which contains the following code:
var i18nService = function() {
this.ensureLocaleIsLoaded = function() {
if( !this.existingPromise ) {
this.existingPromise = $q.defer();
var deferred = this.existingPromise;
var userLanguage = $( "body" ).data( "language" );
this.userLanguage = userLanguage;
console.log( "Loading locale '" + userLanguage + "' from server..." );
$http( { method:"get", url:"/i18n/" + userLanguage, cache:true } ).success( function( translations ) {
$rootScope.i18n = translations;
deferred.resolve( $rootScope.i18n );
} );
}
if( $rootScope.i18n ) {
this.existingPromise.resolve( $rootScope.i18n );
}
return this.existingPromise.promise;
};
The idea is that the user would call ensureLocaleIsLoaded and wait for the promise to be resolved. But given that the purpose of the function is to only ensure that the locale is loaded, it would be perfectly fine for the user to invoke it several times.
I'm currently just storing a single promise and resolve it if the user calls the function again after the locale has been successfully retrieved from the server.
From what I can tell, this is working as intended, but I'm wondering if this is a proper approach.
As I understand promises at present, this should be 100% fine. The only thing to understand is that once resolved (or rejected), that is it for a defered object - it is done.
If you call then(...) on its promise again, you immediately get the (first) resolved/rejected result.
Additional calls to resolve() will not have any effect.
Below is an executable snippet that covers those use cases:
var p = new Promise((resolve, reject) => {
resolve(1);
reject(2);
resolve(3);
});
p.then(x => console.log('resolved to ' + x))
.catch(x => console.log('never called ' + x));
p.then(x => console.log('one more ' + x));
p.then(x => console.log('two more ' + x));
p.then(x => console.log('three more ' + x));
I faced the same thing a while ago, indeed a promise can be only resolved once, another tries will do nothing (no error, no warning, no then invocation).
I decided to work it around like this:
getUsers(users => showThem(users));
getUsers(callback){
callback(getCachedUsers())
api.getUsers().then(users => callback(users))
}
just pass your function as a callback and invoke it as many times you wish! Hope that makes sense.
There s no clear way to resolve promises multiple times because since it's resolved it's done. The better approach here is to use observer-observable pattern for example i wrote following code that observes socket client event. You can extend this code to met your need
const evokeObjectMethodWithArgs = (methodName, args) => (src) => src[methodName].apply(null, args);
const hasMethodName = (name) => (target = {}) => typeof target[name] === 'function';
const Observable = function (fn) {
const subscribers = [];
this.subscribe = subscribers.push.bind(subscribers);
const observer = {
next: (...args) => subscribers.filter(hasMethodName('next')).forEach(evokeObjectMethodWithArgs('next', args))
};
setTimeout(() => {
try {
fn(observer);
} catch (e) {
subscribers.filter(hasMethodName('error')).forEach(evokeObjectMethodWithArgs('error', e));
}
});
};
const fromEvent = (target, eventName) => new Observable((obs) => target.on(eventName, obs.next));
fromEvent(client, 'document:save').subscribe({
async next(document, docName) {
await writeFilePromise(resolve(dataDir, `${docName}`), document);
client.emit('document:save', document);
}
});
If you need to change the return value of promise, simply return new value in then and chain next then/catch on it
var p1 = new Promise((resolve, reject) => { resolve(1) });
var p2 = p1.then(v => {
console.log("First then, value is", v);
return 2;
});
p2.then(v => {
console.log("Second then, value is", v);
});
You can write tests to confirm the behavior.
By running the following test you can conclude that
The resolve()/reject() call never throw error.
Once settled (rejected), the resolved value (rejected error) will be preserved
regardless of following resolve() or reject() calls.
You can also check my blog post for details.
/* eslint-disable prefer-promise-reject-errors */
const flipPromise = require('flip-promise').default
describe('promise', () => {
test('error catch with resolve', () => new Promise(async (rs, rj) => {
const getPromise = () => new Promise(resolve => {
try {
resolve()
} catch (err) {
rj('error caught in unexpected location')
}
})
try {
await getPromise()
throw new Error('error thrown out side')
} catch (e) {
rs('error caught in expected location')
}
}))
test('error catch with reject', () => new Promise(async (rs, rj) => {
const getPromise = () => new Promise((_resolve, reject) => {
try {
reject()
} catch (err) {
rj('error caught in unexpected location')
}
})
try {
await getPromise()
} catch (e) {
try {
throw new Error('error thrown out side')
} catch (e){
rs('error caught in expected location')
}
}
}))
test('await multiple times resolved promise', async () => {
const pr = Promise.resolve(1)
expect(await pr).toBe(1)
expect(await pr).toBe(1)
})
test('await multiple times rejected promise', async () => {
const pr = Promise.reject(1)
expect(await flipPromise(pr)).toBe(1)
expect(await flipPromise(pr)).toBe(1)
})
test('resolve multiple times', async () => {
const pr = new Promise(resolve => {
resolve(1)
resolve(2)
resolve(3)
})
expect(await pr).toBe(1)
})
test('resolve then reject', async () => {
const pr = new Promise((resolve, reject) => {
resolve(1)
resolve(2)
resolve(3)
reject(4)
})
expect(await pr).toBe(1)
})
test('reject multiple times', async () => {
const pr = new Promise((_resolve, reject) => {
reject(1)
reject(2)
reject(3)
})
expect(await flipPromise(pr)).toBe(1)
})
test('reject then resolve', async () => {
const pr = new Promise((resolve, reject) => {
reject(1)
reject(2)
reject(3)
resolve(4)
})
expect(await flipPromise(pr)).toBe(1)
})
test('constructor is not async', async () => {
let val
let val1
const pr = new Promise(resolve => {
val = 1
setTimeout(() => {
resolve()
val1 = 2
})
})
expect(val).toBe(1)
expect(val1).toBeUndefined()
await pr
expect(val).toBe(1)
expect(val1).toBe(2)
})
})
What you should do is put an ng-if on your main ng-outlet and show a loading spinner instead. Once your locale is loaded the you show the outlet and let the component hierarchy render. This way all of your application can assume that the locale is loaded and no checks are necessary.
No. It is not safe to resolve/reject promise multiple times. It is basically a bug, that is hard to catch, becasue it can be not always reproducible.
There is pattern that can be used to trace such issues in debug time. Great lecture on this topic: Ruben Bridgewater — Error handling: doing it right! (the part related to the question is around 40 min)
see github gist: reuse_promise.js
/*
reuse a promise for multiple resolve()s since promises only resolve once and then never again
*/
import React, { useEffect, useState } from 'react'
export default () => {
const [somePromise, setSomePromise] = useState(promiseCreator())
useEffect(() => {
somePromise.then(data => {
// do things here
setSomePromise(promiseCreator())
})
}, [somePromise])
}
const promiseCreator = () => {
return new Promise((resolve, reject) => {
// do things
resolve(/*data*/)
})
}

Categories