I'm trying to test my Promise with chai. My promise returns an array of objects like this:
[
{id: 1, Location: 'some where'},
{id: 2, Location: 'over the rainbow'},
]
I keep getting this error but my test still passes:
UnhandledPromiseRejectionWarning: AssertionError: expected [ Array(7) ] to have deep property 'Location'
My code:
describe('Function myPromise', () => {
it(`should returns object with property Location`, () => {
expect(myPromise(myParams)).to.eventually.have.deep.property('Location');
});
});
I've also tried:
to.eventually.have.deep.property.include('Location');
to.eventually.have.property('Location');
to.eventually.be.an('array').and.have.property('Location');
return expect(myPromise(myParams)).to.eventually.have('Location');
have.deep.property is applicable for checking object but seems like you checked the array.
Let me illustrate with a simple example
const fixtures = [
{id: 1, Location: 'some where'},
{id: 2, Location: 'over the rainbow'},
];
// I tested the first element
expect(fixtures[0]).to.have.deep.property('Location'); // passed
If you want to check every single element, you probably need a loop.
Updates
To use loop to check every element, the first thing to do is to grab the fixtures from the promise function. In this way, I'm using async/await but you can use promise too.
describe("Function myPromise", () => {
it(`should returns object with property Location`, async () => {
const fixtures = await myPromise(myParams); // get the fixtures first
// looping and check each element
fixtures.forEach(fixture =>
expect(fixture).to.have.deep.property("Location"); // no need to use `eventually.have.deep`
);
});
});
Hope it helps
I've figured out how to avoid that error. It's simpler and more straightforward than I thought:
describe('Function myPromise', () => {
it(`should returns object with property Location`, () => {
myPromise(myParams)
.then((results) => {
expect(results).to.be.an('array');
results.forEach((result) => {
expect(result).to.have.property('Location');
});
});
});
});
You can try using Joi (or chai-joi in your case) to make schema validation very easy. I would definitely try this if you're going to be having many tests with object schema validation.
Well a promise is handled asynchronously. I believe you are missing the .then() method in which will return the promise(rejected or fulfilled).
describe('Function myPromise', () => {
it(`should returns object with property Location`, () => {
expect(myPromise(myParams).then(data => data).to.eventually.have.deep.property('Location');
});
});
Related
I'm quite new with jest testing and I'm having trouble to understand how Jest deals with the functions I'm trying to test. Here's my problem:
I'm trying to test the following, quite simple function, which will receive a bookId and will find the object within an array that containa such id. All is vanilla js, no react.
function catchSelectedBook(bookId) {
const objectSearchAsString = localStorage.getItem('objecttransfer');
const booksObject = JSON.parse(objectSearchAsString);
const currentBook = booksObject.filter((book) => book.id === bookId);
return currentBook;
}
The unit test Jest code is the following:
describe('Given a function that is given as argument the id', () => {
test('When invoked, it finds the book that matches such id', () => {
const returnMokObject = {
kind: 'books#volumes',
totalItems: 1080,
items: [
{
kind: 'books#volume',
id: 'HN1dzQEACAAJ',
}],
};
mockLocalstorageJest();
const answer = catchSelectedBook('HN1dzQEACAAJ');
expect(answer.id).toBe('HN1dzQEACAAJ');
});
});
The function mockLocalstorageJest sends to the local storage an object so that it can be get when the function catchSelectedBook is tested
I export my function like this:
module.exports = {
catchSelectedBook,mockLocalstorageJest,
};
And I import the function into the test file like this:
const { catchSelectedBook, mockLocalstorageJest} = require('./book-details.js');
Whenever I run the test, I got the following error message:
enter image description here
Does that mean that Jest doesn't have the array method "filter" defined?
Thanks!
I think that this instruction is returning null
const objectSearchAsString = localStorage.getItem('objecttransfer');
JSON.parse(null) returns null so bookObject is also null.
I believe that your problem is that you are not setting up the local storage correctly in mockLocalstorageJest().
I'm testing a function to see if, when called, it will return the proper created list.
To start, I create the elements, using the createDesign.execute() functions. It's tested on another file and working.
Then, I call the function I want to test: listAllDesigns.execute() and store it's value in a variable.
If I console.log(list), it returns the full list properly.
In pseudocode, what I'd like to do is: Expect list array to have an element with the design object and, within it, a design_id that equals "payload3".
How should I write this test?
Is there a better way to do this? (other than checking if list !== empty, please)
it('should return a list of all designs', async () => {
// Create fake payloads
const payload1 = {
...defaultPayload,
...{ design: { ...defaultPayload.design, design_id: 'payload1' } },
};
const payload2 = {
...defaultPayload,
...{ design: { ...defaultPayload.design, design_id: 'payload2' } },
};
const payload3 = {
...defaultPayload,
...{ design: { ...defaultPayload.design, design_id: 'payload3' } },
};
await createDesign.execute(payload1);
await createDesign.execute(payload2);
await createDesign.execute(payload3);
const list = await listAllDesigns.execute();
// expect(list). ????
});
The easiest method would be a combination of expect.arrayContaining and expect.objectContaining like so:
expect(list).toEqual(
expect.arrayContaining([
expect.objectContaining({
design: expect.objectContaining({
design_id: "payload3"
})
})
])
);
I have an api call that produces the follow results in the console (after pairing it down using map() ).
{…}
CHANGE24HOUR: "$ 11.16"
CHANGEDAY: "$ 3.31"
CHANGEPCT24HOUR: "6.73"
CHANGEPCTDAY: "1.90"
FROMSYMBOL: "Ξ"
HIGH24HOUR: "$ 183.38"
HIGHDAY: "$ 183.38"
However, no matter what I try I can't get at it's properties. The object is called 'coinStats'.
I have tried the following:
coinStats.HIGHDAY = undefined
coinStats['HIGHDAY'] = undefined
coinStats["HIGHDAY"] = undefined
Tried to convert it to an array to see if that would help using
Object.values(coinStats) // Would not work
I am sure the answer is so simplistic. But I am just not sure what it is?
The original raw api results are in the following shape:
(1) […]
0: Object { CoinInfo: {…}, RAW: {…}, DISPLAY: {…} }
length: 1
<prototype>: [
The info I am looking for is in DISPLAY -> USD. I used a map() function to return that sub-object.
The code I am using to fetch the data is essentially the following:
const API = 'https://min-api.cryptocompare.com/data/top/mktcapfull?tsym=USD&page=1';
fetch(API)
.then(results => results.json())
.then(coinData => {
const view = coinData.Data.filter(obj => { return obj.CoinInfo.Name === TRX});
})
const coinFeedAbridged = view.map(item => item.DISPLAY.USD);
const coinStats = coinFeedAbridged[0];
console.dir(coinStats);
I can't access coinStats.HIGHDAY for example... I get 'undefined'.
The fetch call is async, so you can't access the view variable until the call is finished. The code calls the async function and runs the next line, so view variable will be undefined. You have to handle the data inside the fetch callback function, where the data is known.
const API = 'https://min-api.cryptocompare.com/data/top/mktcapfull?tsym=USD&page=1';
fetch(API)
.then(results => results.json())
.then(coinData => {
const view = coinData.Data.filter(obj => {
return obj.CoinInfo.Name === 'TRX'
});
const coinFeedAbridged = view.map(item => item.DISPLAY.USD);
const coinStats = coinFeedAbridged[0];
console.log(coinStats);
})
}
You can test it out in this fiddle. https://jsfiddle.net/gran7ptk/1/
Change line - Add inverted commas to Text "TRX".
const view = coinData.Data.filter(obj => { return obj.CoinInfo.Name
=== TRX});
to
const view = coinData.Data.filter(obj => { return obj.CoinInfo.Name === "TRX"});
I am trying to learn the map method. If I use this syntax response.data.map(d => I am able to iterate data array and see the results, but if I use this syntax response.data.map(([label, CustomStep]) => {, I am getting the error below:
Unhandled Rejection (TypeError): Invalid attempt to destructure non-iterable instance
Can you tell me how to fix it, so that in future I will fix it myself?
Providing my code snippet below:
axios
.get('http://world/sports/values')
.then(response => {
console.log("sports--->", response.data.map(d => d.customFieldValueName));
//this.setState({ playerRanks: response.data.map(d => d.customFieldValueName) });
// es6 map
//Unhandled Rejection (TypeError): Invalid attempt to destructure non-iterable instance
this.setState({
playerRanks: response.data.map(([label, CustomStep]) => {
label.customFieldValueName
})
})
})
update 1:
hey, I saw in console, data is an array inside that there are so many objects
data: Array(19)
[
{
"customFieldValueCode": "player1",
"customFieldValueName": "player1",
"isActive": "Y"
},
{
"customFieldValueCode": "player 2",
"customFieldValueName": "player 2",
"isActive": "Y"
}
]
EDIT:
Based off the data structure provided you could modify your code to...
axios
.get('http://world/sports/values')
.then(response => {
this.setState({
playerRanks: response.data.map(obj => {
return obj.customFieldValueName
})
})
})
OR
...
response.data.map(({customFieldValueName}) => {
return customFieldValueName;
})
...
OR even...
...
response.data.map(({customFieldValueName}) => customFieldValueName)
...
But this would be my recommended solution to provide type checking on you data and proper error handling...
axios
.get('http://world/sports/values')
.catch(err=> console.log(err))
.then(({data}) => { // Axios always returns an Object, so I can safely 'attempt' to destructure 'data' property
if (data && data.length) { // making sure 'data' does exist, it is an Array and has > 0 elements
this.setState({
playerRanks: data.map(obj => { // Not destructuring here in case obj isn't actually an Object
if (obj && obj.customFieldValueName) return customFieldValueName;
return null;
}).filter(elem=> elem) // BIG-O notation: This sequence is O(2N), as in iterates over the entire Array first with .map(), then iterates over the entire Array again with .filter() to clear out 'null' values
})
}
})
In order to prevent your returned Array above from having a bunch of null elements when they don't conform to our assertions, you can use an Array.reduce() method to 'filter' out any nulls...
axios
.get('http://world/sports/values')
.catch(err=> console.log(err))
.then(({data}) => { // Axios always returns an Object, so I can safely 'attempt' to destructure 'data' property
if (data && data.length) { // making sure 'data' does exist, it is an Array and has > 0 elements
this.setState({
playerRanks: data.reduce((acc,obj) => { // Not destructuring here in case obj isn't actually an Object
if (!obj || !obj.customFieldValueName) return acc; // If it doesn't meet assertions just return the existing accumulator (don't add another element .ie 'null')
return [
...acc, // If it conforms to the assertions the return a new accumulator, by first spreading in all existing elements and the adding the new one (customFieldValueName)
customFieldValueName
]
},[]) // BIG-O notation: This is O(1N) or O(N), as in it will only iterate over the Array one time and the reduce() function will filter out 'null' values at the same time
})
}
})
NOTE:
I also just added .filter(elem=> elem) to the end of my first example, which does the same thing as the new .reduce() functionality, but does this in 1N not 2N operations.
PRE-logged data
Here's how the Array.map() method works...
[1,2].map(element=> {
// element === 1, first iteration,
// element === 2, second iteration
})
Here's how Array destructuring works...
[one, two, ...theRest] = [1,2,3,4,5]
// one === 1 and two === 2 and theRest = [3,4,5]
Here's how Object destructuring works...
{one, three, ...theRest} = {one: 1, two: 2, three: 3, four: 4, five: 5}
// one === 1 and three === 3 and theRest === {two: 2, four: 4, five: 5}
// notice order doesn't matter here (three vs two), but you need to access valid properties from the object you're deetructuring from
So based on the way you function is structured you are making the assumption that the data structure of response.data is...
response.data === [
[
{ customFieldValueName: 'any value' }, // label
{} // CustomStep (this could be any value, not necessarily an Object)
],
[
{ customFieldValueName: 'any value' }, // label
'any value' // CustomStep
]
]
I hope this helps conceptually, but if you'd like a workable solution we will need...
Data structure of response.data. Can you provide result of console.log( JSON.stringify( response.data, null, 5) )
Specific values you are trying to assign to the new this.state.playerRanks Array.
PS: A good way to see Object destructuring in action with your current code is to change...
.then( response => {
To
.then( ({data}) => {
In this case, you should be certain that response.data is an array of arrays, because for each iteration of response.data.map, the function you are providing to the map must receive an array to be able to successfully pull the label and CustomStep values, due to the syntax with which you are destructuring the function parameter.
Imagine data in the following example is the response.data and the parseData function is the function you are passing to the map:
let data = [
[{ customFieldValueName: 'field name' }, { stepData: {} }],
[{ customFieldValueName: 'another field name' }, { stepData: {} }]
];
let parseData = ([label, CustomStep]) => console.log(label.customFieldValueName);
parseData(data[0]); // prints out 'field name'
Otherwise, if response.data is an array of objects, which it seems like it is due to you successfully being able to run response.data.map(d => d.customFieldValueName), you could update your map to this (if you simply want to pull the customFieldValueName value out of the object):
response.data.map(({ customFieldValueName }) => customFieldValueName)
I have an array of promises, and I'm trying to push new promises into that array inside of another dispatch.then function, but it appears that the array is always out of scope
load(params, auth) {
return dispatch => {
const { passage, versions, language_tag } = params
let promises = []
versions.forEach((id) => {
// get the version info, and then pass it along
dispatch(ActionCreators.version({ id: id })).bind(promises).then((version) => {
promises.push(dispatch(ActionCreators.passages({
id: id,
references: [passage],
versionInfo: {
local_abbreviation: version.abbreviation,
local_title: version.title,
id: version.id,
},
})))
})
})
//
promises.push(dispatch(ActionCreators.configuration()))
promises.push(dispatch(ActionCreators.byRef({ language_tag })))
console.log(promises.length)
return Promise.all(promises)
}
},
I've tried a few different approaches, such as setting var that = this right before the dispatch inside of the versions loop, and what is shown here, trying to use .bind(promises) on the dispatch.
promises.length is always 2, (because of the two that are actually getting pushed at the bottom). I can console statements inside of the .then so I know it's getting executed, but the dispatches are not ending up in the promises array.
I could very well be thinking of the dispatch function in an incorrect way.
Any help would be appreciated!
The problem is that since you're adding the promises on then(), you have already returned the array by the time you're adding the promises. So they do get added, but too late.
Instead, try this:
load(params, auth) {
return dispatch => {
const { passage, versions, language_tag } = params;
let promises = [];
versions.forEach((id) => {
// get the version info, and then pass it along
promises.push(dispatch(ActionCreators.version({ id: id })).then((version) => {
return dispatch(ActionCreators.passages({
id: id,
references: [passage],
versionInfo: {
local_abbreviation: version.abbreviation,
local_title: version.title,
id: version.id,
},
}));
}));
});
//
promises.push(dispatch(ActionCreators.configuration()));
promises.push(dispatch(ActionCreators.byRef({ language_tag })));
console.log(promises.length);
return Promise.all(promises)
}
}