I have a suite of tests but something is just not clicking regarding callback assertions. My feeling is that the done() parameter needs to be woven in, but I'm doing it wrong.
I basically have two function structures, where the callback is either nested inside of a single then statements, or a then inside of another then:
function foo(cb) {
return fetch('foo.com')
.then(
(res)=>{
res
.json()
.then(
(data) =>cb(data)
})
.catch(err => console.error(err))
}
and
function foo(cb) {
return fetch('foo.com')
.then(()=>cb())
.catch(err => console.error(err))
}
I'm looking to assert that the callback was called in both cases.
I have tried
describe('ugh why can't I figure this out'?, () => {
it('is confusing', (done) => {
const testCb = jest.fn()
foo(testCb);
expect(testCb).toHaveBeenCalled()
done();
//failed: expected testCb to be called, but it was not called
}
})
I'm not sure how to move forward- I'm not a fan of the spaghetti on the wall approach, so I'd rather understand how to implement jest for testing async code before I just start switching in different syntax. The above code seems like it should work because the callback is part of the function execution, so if the higher order function executes, the callback would necessarily be executed, and should have been "called". Obviously this reasoning is wrong since the test isn't passing, but I'm not quite seeing the way around this.
Thanks very much for any direction/insight :).
This jest example seems to match mine- what am I missing?
describe('drinkAll', () => {
test('drinks something lemon-flavored', () => {
let drink = jest.fn();
drinkAll(drink, 'lemon');
expect(drink).toHaveBeenCalled();
});
test('does not drink something octopus-flavored', () => {
let drink = jest.fn();
drinkAll(drink, 'octopus');
expect(drink).not.toHaveBeenCalled();
});
});
You're expect is being called before the fetch comes back. Do this:
foo(testCb)
.then(_ => {
expect(testCb).toHaveBeenCalled();
done();
})
.catch(err => {
done.fail(err);
});
By chaining on to the Promise returned by foo we ensure the fetch has come back. Once you go async you have to stay that way, you can't mix sync and async code like you did in your posted code:
const testCb = jest.fn()
foo(testCb); // this can take an arbitrary amt of time
expect(testCb).toHaveBeenCalled() // but this happens immediately
done();
FWIW you can also change this
return fetch('foo.com')
.then(
(res)=>{
res
.json()
.then(
(data) =>cb(data)
})
Into this:
return fetch('foo.com')
.then((res)=> res.json())
.then(cb)
.catch((err) => ...
The extra level of nesting promises is unnecessary and makes the code hard to read.
Related
I have been studying promises, await and async functions. While I was just in the stage of learning promises, I realized that the following: When I would send out two requests, there was no guarantee that they would come in the order that they are written in the code. Of course, with routing and packets of a network. When I ran the code below, the requests would resolve in no specific order.
const getCountry = async country => {
await fetch(`https://restcountries.com/v2/name/${country}`)
.then(res => res.json())
.then(data => {
console.log(data[0]);
})
.catch(err => err.message);
};
getCountry('portugal');
getCountry('ecuador');
At this point, I hadn't learned about async and await. So, the following code works the exact way I want it. Each request, waits until the other one is done.
Is this the most simple way to do it? Are there any redundancies that I could remove? I don't need a ton of alternate examples; unless I am doing something wrong.
await fetch(`https://restcountries.com/v2/name/${country}`)
.then(res => res.json())
.then(data => {
console.log(data[0]);
})
.catch(err => err.message);
};
const getCountryData = async function () {
await getCountry('portugal');
await getCountry('ecuador');
};
getCountryData();
Thanks in advance,
Yes, that's the correct way to do so. Do realize though that you're blocking each request so they run one at a time, causing inefficiency. As I mentioned, the beauty of JavaScript is its asynchronism, so take advantage of it. You can run all the requests almost concurrently, causing your requests to speed up drastically. Take this example:
// get results...
const getCountry = async country => {
const res = await fetch(`https://restcountries.com/v2/name/${country}`);
const json = res.json();
return json;
};
const getCountryData = async countries => {
const proms = countries.map(getCountry); // create an array of promises
const res = await Promise.all(proms); // wait for all promises to complete
// get the first value from the returned array
return res.map(r => r[0]);
};
// demo:
getCountryData(['portugal', 'ecuador']).then(console.log);
// it orders by the countries you ordered
getCountryData(['ecuador', 'portugal']).then(console.log);
// get lots of countries with speed
getCountryData(['mexico', 'china', 'france', 'germany', 'ecaudor']).then(console.log);
Edit: I just realized that Promise.all auto-orders the promises for you, so no need to add an extra sort function. Here's the sort fn anyways for reference if you take a different appoach:
myArr.sort((a, b) =>
(countries.indexOf(a.name.toLowerCase()) > countries.indexOf(b.name.toLowerCase())) ? 1 :
(countries.indexOf(a.name.toLowerCase()) < countries.indexOf(b.name.toLowerCase()))) ? -1 :
0
);
I tried it the way #deceze recommended and it works fine: I removed all of the .then and replaced them with await. A lot cleaner this way. Now I can use normal try and catch blocks.
// GET COUNTRIES IN ORDER
const getCountry = async country => {
try {
const status = await fetch(`https://restcountries.com/v2/name/${country}`);
const data = await status.json();
renderCountry(data[0]); // Data is here. Now Render HTML
} catch (err) {
console.log(err.name, err.message);
}
};
const getCountryData = async function () {
await getCountry('portugal');
await getCountry('Ecuador');
};
btn.addEventListener('click', function () {
getCountryData();
});
Thank you all.
I am in my cart actions and I am calling loadCartItems() from another function within the same file. However, this function is not returning a promise or any data and I do not know why. My loadCartItems() function is not even being recongized as a function actually. Does anyone know why this might be?
export function loadCartItems() {
return (dispatch, getState) => {
dispatch({
type: types.LOAD_CART_PRODUCTS
});
return AsyncStorage.getItem(STORAGE_KEY_JWT_TOKEN).then((key) => {
return API.getCartItems(key)
.then((response) => {
return dispatch({
type: types.LOAD_CART_PRODUCTS_SUCCESS,
response
});
}).catch(err => {
console.log('Error retrieving cart products');
})
}).catch(err => {
console.log("Error retrieving cart items from local storage");
});
};
}
export function getUnaddedCartItems() {
return (dispatch, getState) => {
dispatch({
type: types.GET_UNADDED_ITEMS
});
return AsyncStorage.getItem(STORAGE_KEY_CART_ITEMS).then((result) => {
const addedItems = JSON.parse(result);
loadCartItems()
.then((result) => {
const cartItems = result.response.products;
if (this.state.unaddedCartItems.length === 0) {
const unaddedCartItems = addedItems.filter((addedItem) => {
return cartItems.find(cartItem => cartItem.id !== addedItem.productId);
});
}
}).catch(err => {
consoel.log('error: ', err);
});
}).catch(error => {
console.log('error: ', error);
});
};
}
this.loadCartItems() isn't a function. loadCartItems() is.
Since they aren't in a common class/object/something, there is no need to use this. It basically acts like a global (within the context of that file), so just call it directly.
Looking at it a bit closer, it looks like you are trying to call an action creator within an action creator. That's your problem.
Normally, you map these actions within your component, so it takes care of the dispatch bit for you. However, when you are calling the function directly yourself, you need to also deal with it yourself.
loadCartItems().then is the thing that isn't a function now that you've removed the this. That makes sense, since loadCartItems() actually returns a function, not a Promise. The function accepts two arguments: dispatch and getState.
You need to call it like this: loadCartItems()(dispatch, getState).then().
It doesn't actually say loadCartItems() is not a function
It says loadCartItems(...).then is not a function.
What does it mean?
In fact, loadCartItems(...).then is not a function, because the function doesn't return a Promise. It returns another function!
As stated in redux-thunk docs:
Any return value from the inner function will be available as the return value of dispatch itself.
So, in order to properly call your loadCartItems() action, you should do
dispatch(loadCartItems(anyParamYouWant)).then(...)
I'd recommend you to take a look at redux-thunk docs to help you get a better understanding on how thunks works ;)
Both loadItems and getUnaddedCartItems return thunks, not promises.
They therefore need to be dispatched first, so that they return the promises you're expecting.
That dispatch code before you return the promise seems to be unnecessary so if you in fact don't need it, just have the functions return the promises.
I have a API script in a file
const ApiCall = {
fetchData: async (url) => {
const result = await fetch(url);
if (!result.ok) {
const body = await result.text(); // uncovered line
throw new Error(`Error fetching ${url}: ${result.status} ${result.statusText} - ${body}`); // uncovered line
}
return result.json();
},
};
export default ApiCall;
When I mock the call, I have two uncovered lines in code coverage.
Any idea how can I make them cover as well.
Here is what I have tried so far which is not working
it('test', async () => {
ApiCall.fetchData = jest.fn();
ApiCall.fetchData.result = { ok: false };
});
I am kind of new into Jest, so any help would be great.
You need to provide a stubb response in your test spec so that the if statement is triggered. https://www.npmjs.com/package/jest-fetch-mock will allow you to do just that. The example on their npm page should give you what you need https://www.npmjs.com/package/jest-fetch-mock#example-1---mocking-all-fetches
Basically the result is stored in state(redux) and is called from there. jest-fetch-mock overrides your api call/route and returns the stored result in redux all within the framework.
Assuming that what you want to test is the ApiCall then you would need to mock fetch. You are mocking the entire ApiCall so those lines will never execute.
Also, you have an issue, because if you find an error or promise rejection, the json() won't be available so that line will trigger an error.
Try this (haven't test it):
it('test error', (done) => {
let promise = Promise.reject(new Error("test"));
global.fetch = jest.fn(() => promise); //You might need to store the original fetch before swapping this
ApiCall.fetchData()
.catch(err => );
expect(err.message).toEqual("test");
done();
});
it('test OK', (done) => {
let promise = Promise.resolve({
json: jest.fn(() => {data: "data"})
});
global.fetch = jest.fn(() => promise);
ApiCall.fetchData()
.then(response => );
expect(response.data).toEqual("data");
done();
});
That probably won't work right away but hopefully you will get the idea. In this case, you already are working with a promise so see that I added the done() callback in the test, so you can tell jest you finished processing. There is another way to also make jest wait for the promise which is something like "return promise.then()".
Plese post back
I had hard times testing promise based functions. I am using Mocha as test framework and chai library for assertion framework. I new to both Mocha and chai. I had few problems and I do not know that is wrong with my code.Maybe my testing approach totally wrong, maybe someone help em.
I get Uncaught (in promise) error for expect, but actually I do not know whether my way is the correct way of testing these functions.
Here is my returnResult function which resolves a value -> a string
var returnResult = function (stateParamName, stateParamValue) {
return new Promise((resolve, reject) => {
peer3.get({ path: { equals: 'todo/#0' } }).then(function (results) {
console.log(stateParamName + ': ' + results[0].value[stateParamName])
resolve(results[0].value[stateParamName]);
});
});
}
And here is my mocha test
describe("Call Tests Suite", function () {
describe("Basic Call Test", function () {
it("Call status for both side should be IN CALL", function () {
peer3.call('login/add', ['testaccount1'])
.then(() => peer3.call('todo/makeCall1', ['testaccount2#testdomain.com']))
.then(() => checkResult('state_term', 'RINGING'))
.then((interval) => { clearInterval(interval); peer3.call('todo/answerCall2', ['empty']) })
.then(() => checkResult('state_term', 'IN_CALL'))
.then((interval) => clearInterval(interval))
//.then(console.log('test sonucu: ' + returnResult('state_term', 'IN_CALL')))
.then(returnResult('state_term', 'IN_CALL'))
.then((result) => expect(result).to.equal('IN_CALL'))
});
});
As you see I am only using assert for last result. Maybe I should test whole test as an promise function. Can someone help me on this ?
I don't know from where your error come from. However, there are a lot of things in your code you can improve, and may lead you to a better debugging, so you can find the error:
1- You should add a .catch handler at the end of the promise chain. The 'uncaught error' refers to this: you have an error in one of your then handlers but it is not caught in a catch. You should add a catch call at the end of the train:
describe("Call Tests Suite", function () {
describe("Basic Call Test", function () {
it("Call status for both side should be IN CALL", function () {
peer3.call('login/add', ['testaccount1'])
.then(() => peer3.call('todo/makeCall1', ['testaccount2#testdomain.com']))
.then(() => checkResult('state_term', 'RINGING'))
.then((interval) => { clearInterval(interval); peer3.call('todo/answerCall2', ['empty']) })
.then(() => checkResult('state_term', 'IN_CALL'))
.then((interval) => clearInterval(interval))
//.then(console.log('test sonucu: ' + returnResult('state_term', 'IN_CALL')))
.then(returnResult('state_term', 'IN_CALL'))
.then((result) => expect(result).to.equal('IN_CALL'))
.catch(err => {
console.log(err);//This will allow you better debugging.
})
});
});
Ok, now, we have to keep in mind that your code is asynchronous. However, mocha it functions, by default, are synchronous: they will not wait for your async code to execute.
In order to tell mocha that your test is asynchronous, you have to pass a parameter to the test. This parameter is a function, usually called done, that you must explicitly call when your test finish. Otherwise, your test will finish before reaching the asynchronous part of your code, usually giving you false positives.
describe("Call Tests Suite", function () {
describe("Basic Call Test", function () {
it("Call status for both side should be IN CALL", function (done) {
peer3.call('login/add', ['testaccount1'])
.then(() => peer3.call('todo/makeCall1', ['testaccount2#testdomain.com']))
.then(() => checkResult('state_term', 'RINGING'))
.then((interval) => { clearInterval(interval); peer3.call('todo/answerCall2', ['empty']) })
.then(() => checkResult('state_term', 'IN_CALL'))
.then((interval) => clearInterval(interval))
//.then(console.log('test sonucu: ' + returnResult('state_term', 'IN_CALL')))
.then(returnResult('state_term', 'IN_CALL'))
.then((result) => expect(result).to.equal('IN_CALL'))
.then( () => {
done(); //dont use .then(done) or things may break due to extra
parameter
})
.catch( err => {
console.log(err);
done(err); //passing a parameter to done makes the test fail.
})
});
});
Still, there are an issue with your code we must address. The then method expects a function as a parameter. However, in this line:
.then(returnResult('state_term', 'IN_CALL'))
You are passing it a call to returnResult('state_term', 'IN_CALL'), which return not a function, but a promise. You should pass a function instead -and then return the promise-:
.then(() => returnResult('state_term', 'IN_CALL')) //now the parameter to
//then is a function that return a Promise, not a Promise itself.
Also, as you've been told in the commments, you're explicitly returning a new Promise that wraps a Promise in your return result function: that is not needed at all and that function could be written as so:
var returnResult = function (stateParamName, stateParamValue) {
return peer3.get({ path: { equals: 'todo/#0' } }).then(function (results) {
console.log(stateParamName + ': ' + results[0].value[stateParamName]);
return(results[0].value([stateParamName]));
});
}
However, there are a lot of things in your code that seems really odd (I'am not sure at all why the setinterval calls are there), so I'm pretty confident the test will not work as you expect. You should start by familiarizing yourself with Promises and asynchronous mocha tests, and don't try to test really long sequences of asynchronous operations. good luck!
I'm trying to test promises-chain sequence with Jest:
someChainPromisesMethod: function() {
async()
.then(async1)
.then(async2)
.then(result)
.catch(error);
}
While testing single promise is good documented not sure what is a proper way (not sure what to do TBO) to test this kind of chain. Let's assume all asyncs are mocked and just resolves promises (Promise.resolve) in theirs body.
So I need something that will test whole sequence.
You can use jest.fn() to mock implementation and check what the function has been called with and return what you want. You need to mock all async functions you have in your function and return what you want.
e.g.
async = jest.fn(() => {
return Promise.resolve('value');
});
async1 = jest.fn(() => {
return Promise.resolve('value1');
});
async2 = jest.fn(() => {
return Promise.resolve('Final Value');
});
You can use this in your test as
it('should your test scenario', (done) => {
someChainPromisesMethod()
.then(data => {
expect(async1).toBeCalledWith('value');
expect(async2).toBeCalledWith('value1');
expect(data).toEqual('Final Value');
done();
});
});
However, I would flatten your chain and test them separately if you have logic in them, that way you can test all possibilities easily.
Using done does not fix the issue, it will give you a false positive test. If for any reason an expectation fails, your test will timeout and you'll not have the real result.
The right solution is to return your Promise, so Jest will be able to evaluate the expect result correctly.
Following the #grgmo example, a better approach could be:
it('should your test scenario', () => {
return someChainPromisesMethod()
.then(data => {
expect(async1).toBeCalledWith('value');
expect(async2).toBeCalledWith('value1');
expect(data).toEqual('Final Value');
});
});