I am setting up my tests for the results to a REST endpoint that returns me an array of Mongo database objects.
[{_id: 5, title: 'Blah', owner: 'Ted', description: 'something'...},
{_id: 70, title: 'GGG', owner: 'Ted', description: 'something'...}...]
What I want my tests to verify is that in the return array it conatins the specific titles that should return. Nothing I do using Chai/Chai-Things seems to work. Things like res.body.savedResults.should.include.something.that.equals({title: 'Blah'}) error out I'm assuming since the record object contains other keys and values besides just title.
Is there a way to make it do what I want? I just need to verify that the titles are in the array and don't care what the other data might be (IE _id).
Thanks
This is what I usually do within the test:
var result = query_result;
var members = [];
result.forEach(function(e){
members.push(e.title);
});
expect(members).to.have.members(['expected_title_1','expected_title_2']);
If you know the order of the return array you could also do this:
expect(result).to.have.deep.property('[0].title', 'expected_title_1');
expect(result).to.have.deep.property('[1].title', 'expected_title_2');
As stated here following code works now with chai-like#0.2.14 and chai-things. I just love the natural readability of this approach.
var chai = require('chai'),
expect = chai.expect;
chai.use(require('chai-like'));
chai.use(require('chai-things')); // Don't swap these two
expect(data).to.be.an('array').that.contains.something.like({title: 'Blah'});
Probably the best way now a days would be to use deep.members property
This checks for unordered complete equality. (for incomplete equality change members for includes)
i.e.
expect([ {a:1} ]).to.have.deep.members([ {a:1} ]); // passes
expect([ {a:1} ]).to.have.members([ {a:1} ]); // fails
Here is a great article on testing arrays and objects
https://medium.com/building-ibotta/testing-arrays-and-objects-with-chai-js-4b372310fe6d
DISCLAIMER: this is to not only test the title property, but rather a whole array of objects
ES6+
Clean, functional and without dependencies, simply use a map to filter the key you want to check
something like:
const data = [{_id: 5, title: 'Blah', owner: 'Ted', description: 'something'},{_id: 70, title: 'GGG', owner: 'Ted', description: 'something'}];
expect(data.map(e=>({title:e.title}))).to.include({title:"Blah"});
or even shorter if you only check one key:
expect(data.map(e=>(e.title))).to.include("Blah");
https://www.chaijs.com/api/bdd/
Here is another approach that I found to be more helpful. Basically, use string interpolation and map your array of objects to an array of string literals. Then you can write expectations against the array of strings.
const locations: GeoPoint[] = [
{
latitude: 10,
longitude: 10
},
{
latitude: 9,
longitude: 9
},
{
latitude: -10,
longitude: -10
},
{
latitude: -9,
longitude: -9
}
];
const stringLocations: string[] = locations.map((val: GeoPoint) =>
`${val.latitude},${val.longitude}`
);
expect(stringLocations).to.contain('-9.5,-9.5');
expect(stringLocations).to.contain('9.5,9.5');
I loved the suggestion from #sebastien-horin
But another way with Should syntax (for the specific property):
const data = [
{ _id: 5, title: 'Blah', owner: 'Ted', description: 'something' },
{ _id: 7, title: 'Test', owner: 'Ted', description: 'something' },
];
data.map((e) => e.title).every((title) => title.should.equal('Blah'));
An alternative solution could be extending the array object with a function to test if an object exists inside the array with the desired property matching the expected value, like this
/**
* #return {boolean}
*/
Array.prototype.HasObjectWithPropertyValue = function (key, value) {
for (var i = 0; i < this.length; i++) {
if (this[i][key] === value) return true;
}
return false;
};
(i put this in my main test.js file, so that all other nested tests can use the function)
Then you can use it in your tests like this
var result = query_result;
// in my case (using superagent request) here goes
// var result = res.body;
result.HasObjectWithPropertyValue('property', someValue).should.equal(true);
Related
I am working on something where I take data from 2 different APIs that I have no control of and I want to combine the results in the most efficient way.
One of the arrays hold some assets, lets say books, the other one holds a transaction for the said book. Here is an example:
{
author: {name: 'J.K. Rowling', },
assetName: 'Book1'
}]
const array2 = [
{from: 'John',
to: 'Sarah,
price: 10,
timeStamp: 123,
assetName: 'Book1',
authorName: 'J.K. Rowling'
}]
Note that to find the corresponding transaction for a given book, you need both assetName and authorName to match - you can own more than one book of the same author and you can own two books with the same name but a different author but an author has only one book with a given name, thus finding the corresponding transaction to an asset requires both fields to match and there are no other unique identifiers.
The naive approach is to iterate over one of the arrays and for each entry to check in the second array to find the transaction but that looks like it will take too long to execute if the arrays are of substantial size.
I was wondering what better solutions can you think of for merging two objects with different structure that is efficient?
Well, if author.name + assetName form an id, you could iterate over array1 once & create a Map with keys being author.name + assetName & vales being original objects.
Then you could iterate over array2 once as well & enrich it whatever way you want. All lookups in the second iteration will be fast since you will access the Map instead of searching in array.
const indexedArray1 = new Map();
array1.forEach(data => indexedArray1.set(data.author.name + data.assetName, data);
const enrichedArray2 = array2.map(transaction => {
const relatedBook = indexedArray1.get(transaction.authorName + transaction.assetName);
// Merge relatedBook & transaction the way you want here
});
I often do the following when merging arrays
The time complexity is O(n)
const array1 = [{
author: {name: 'J.K. Rowling' },
assetName: 'Book1'
}]
const array2 = [{
from: 'John',
to: 'Sarah',
price: 10,
timeStamp: 123,
assetName: 'Book1',
authorName: 'J.K. Rowling'
}]
const array2_map = {}
array2.forEach(e => {
const key = `${e.assetName}:${e.authorName}`
if (!array2_map[key]) array2_map[key] = []
const { from, to, price, timeStamp } = e
array2_map[key].push({
from,
to,
price,
timeStamp
})
})
const merged_array = array1.map(e => ({
...e,
transaction: array2_map[`${e.assetName}:${e.authorName}`] || []
}))
I have two JSON documents that I want to assert equal for Jest unit testing. They should be equal, except the second one has one more key: _id.
Example:
doc1.json
{
username: 'someone',
firstName: 'some',
lastName: 'one',
}
doc2.json
{
_id: '901735013857',
username: 'someone',
firstName: 'some',
lastName: 'one',
}
My code currently looks like this:
const result = await activeDirectoryUserCollection
.findOne({username: testUser1.username});
expect(result).toBe(testUser1);
Obviously this gives the error that they are not equal, just because of that one value.
I'm looking for an alternative to .toBe() that doesn't completely compare the docs, but checks if one is a subset of another. (or something like that).
Alternatively I would appreciate someone to point me to a module that could help me out.
I would checkout Lodash module's .isMatch function.
It performs a partial deep comparison between object and source to determine if object contains equivalent property values.
https://lodash.com/docs/4.17.11#isMatch
Example:
var object = { 'a': 1, 'b': 2 };
_.isMatch(object, { 'b': 2 });
// => true
_.isMatch(object, { 'b': 1 });
// => false
You can iterate through one Object and use the key to assert value in both Objects. Read More for...in
const result = await activeDirectoryUserCollection
.findOne({username: testUser1.username});
for (const prop in testUser1) {
if (testUser1[prop]) {
expect(result[prop]).toBe(testUser1[prop]); // or use .toEqual
}
}
I don't think you need to look outside jest for this. You can use expect.objectContaining(), which is described in the docs as:
expect.objectContaining(object) matches any received object that recursively matches the expected properties. That is, the expected object is a subset of the received object. Therefore, it matches a received object which contains properties that are present in the expected object.
You could use it like:
test('objects', () => {
expect(doc2).toEqual(
expect.objectContaining(doc1)
);
});
I have problems with Object.assign and ... spread operator. I need to process values (object with name and value tha are objects).
Example my values object:
{
id: "12",
name: "Hotel MESSI",
email: "myemail#aol.com",
phone: "+001060666661",
otherfields: "{
country: 'ZW',
city: 'Zurick'
}"
}
otherfields comes from graphql , so it's string, i must convert to object.
With my process I look for this result:
{
id: "12",
name: "Hotel MESSI",
email: "myemail#aol.com",
phone: "+001060666661",
country: 'ZW',
city: 'Zurick'
}
The code have more code that I paste here, there is a lot of controls for values and conversion but mainly, the idea is reassing values,
With these two case assign to the same variable is not working:
Case 1, with object.assign
processValues = (values)=>
let newValues = {...values}; //
for (const fieldName in Tables[table].fields) {
let value = values[fieldName];
value = JSON.parse(value);
newValues = { ...newValues, ...value};
console.error('after mix',newValues);
Case 2, with object.assign
processValues = (values)=>
let newValues = Object.assign({}, values}; //
for (const fieldName in Tables[table].fields) {
let value = values[fieldName];
value = JSON.parse(value);
newValues = Object.assign( newValues, value};
console.error('after mix',newValues);
How it's works, when I use a new variable, by example:
newValues2 = Object.assign( newValues, value};
but my idea is not use another variable because , i need to get values and set values for the original variable 'newValues' , if I use another variable the code would be more cumbersome.
I'm using in a project with create-react-app. I don't know if it's a problem with babel, because Object.assign and spread operator are not inmmutable; or yes ?
INFO:
Tables[table].fields is a object with definition por my table structure, there therea lot of rules, but basically i need to know why object and ... does not work
The use of JSON.stringify will not help, as this will produce a JSON string, which will have an entirely different behaviour when spreading it (you get the individual characters of that string).
Here is how you can achieve the result with "otherfields" as the special field (you can add other fields in the array I have used):
const processValues = values =>
Object.assign({}, ...Object.entries(values).map( ([key, val]) =>
["otherfields"].includes(key) ? val : { [key]: val }
));
// Example:
const values = {
id: "12",
name: "Hotel MESSI",
email: "myemail#aol.com",
phone: "+001060666661",
otherfields: {
country: 'ZW',
city: 'Zurick'
}
};
const result = processValues(values);
console.log(result);
The first argument to assign is the target. So it's going to get changed. You can simply pass an empty object for your target if you don't want any of the sources to change.
When you are using first argument as {} then no value will change.
For more please refer it.
https://wecodetheweb.com/2016/02/12/immutable-javascript-using-es6-and-beyond/
I have the following script, which essentially extracts each value from the "data" array being passed and returns json value.
function parsejson(data) {
var temp2 = new Array();
if (data) {
$.each(data, function (i, val) {
vproductid = data[i].productid;
vproductname = data[i].product_name;
vexpirydt = data[i].expiry;
temp2.push({vproductid,vproductname,vexpirydt});
});
console.log([temp2]);
return [temp2];
}
}
So in this case "data" within my console log comes back as:
Array [ Object, Object ]
Which contains the following objects -> values, etc, and in some instances my "expiry" date value is blank space, but still appears to work properly in Firefox and Chrome.
The issues seems to be exclusively with IE 11 in my case.... I keep getting the following Error in IE only which ties back to the "push" to append to my array somehow. I don't know if it's a syntax error or the way in which I'm trying to append to my array, but obviously I'm doing something wrong. My intention is simply to return a second array in json format, so might be a simpler way.
SCRIPT1003: Expected ':'
Comments provide the answear to IE error, here is a sample code that will work on IE and Chrome:
function parsejson(data) {
var temp2 = []; // this syntax might be prefered to create a new array
if (data) {
temp2 = data.map(function(element) {
return {
vproductid: element.productid,
vproductname: element.product_name,
vexpirydt: element.expiry
};
});
}
console.log(temp2);
return temp2;
}
var sampleData = [{
productid: 1,
product_name: 'a',
expiry: 'Today',
someThingelse: '',
}, {
productid: 2,
product_name: 'b',
expiry: 'Today',
someThingelse: '',
}, {
productid: 3,
product_name: 'c',
expiry: 'Today',
someThingelse: '',
}];
parsejson(sampleData);
Using Wikipedia's API I got an object which looks like that:
obj = {["string", Array[10], Array[10], Array[10]]}
The first array contains titles, second is descriptions and third is links.
Some of the descriptions may be empty ("").
Example -
I would like to combine all three arrays into one array as follow:
[{title: obj[1][0], desc: obj[2][0], link: obj[3][0]}, ... , {title: array[1][9], desc: obj[2][0], link: obj[3][0]}]
But only combining if description is not empty - obj[2][i] !== ""
I managed to do it using for loop and if:
var arr = [];
for (var i = 0; i < data[1].length; i++) {
if (data[2][i] !== '') {
arr.push({title: data[1][i], desc:data[2][i], link: data[3][i]});
}
}
Is there a way of achieving the same in a more elegant way using javascript's high order function? such as map and filter (maybe reduce?)
Thanks
EDIT:
The way I did it in the end was:
var resp = ["Football",
['Football', 'Soccer'],
['FootballDescription', 'SoccerDescription'],
['http://football.com', 'http://soccer.com']
];
// Mimic python's zip behavior -> assume arrays are of equal size
function zip(arrays) {
return arrays[0].map(function(_, i){
return arrays.map(function(array){
return array[i];
});
});
}
// 1. Throw away the first string, e.g. "Football"
resp.shift();
// 2. Use zip to make an array of intersected arrays, i.e
// [
// ['Football', 'FootballDescription', 'http://football.com'],
// ['Soccer', 'SoccerDescription', 'http://soccer.com']
// ]
// and then use map to make each array into a nicely named object
var objects = zip(resp).map(function(x) {
return ({ name: x[0], desc: x[1], link: x[2] });
});
console.log(objects);
/*
Output:
[ { name: 'Football',
description: 'FootballDescription',
link: 'http://football.com' },
{ name: 'Soccer',
description: 'SoccerDescription',
link: 'http://soccer.com' } ]
*/
You're about to learn an awesome new higher-order function, zip! To be honest, I very rarely run into situtations where it is useful, but your example happens to be perfect for it.
/*
Example in ES6. In order to run, first install node and then go
npm install -g babel
npm install underscore
babel thisfile.js | node
*/
let _ = require('underscore')
let resp = [
"Football",
['Football', 'Soccer'],
['FootballDescription', 'SoccerDescription'],
['http://football.com', 'http://soccer.com']
]
// 1. Throw away the first string, e.g. "Football"
resp.shift()
// 2. Use zip to make an array of intersected arrays, i.e
// [
// ['Football', 'FootballDescription', 'http://football.com'],
// ['Soccer', 'SoccerDescription', 'http://soccer.com']
// ]
// and then use map to make each array into a nicely named object
let objects = _.zip(
resp[0],
resp[1],
resp[2]
).map(x => ({
name: x[0],
desc: x[1],
link: x[2]
}))
console.log(objects)
/*
Output:
[ { name: 'Football',
description: 'FootballDescription',
link: 'http://football.com' },
{ name: 'Soccer',
description: 'SoccerDescription',
link: 'http://soccer.com' } ]
*/
There's no need to involve another library if you don't want. Map and filter work here but I think they add a lot of length. I'd favor forEach.
Keep in mind that Array.prototype.forEach passes along up to three parameters to your callback function. In this case you can get by with two.
data[2].forEach(function(elem, index) {
elem && arr.push({title: data[1][index], desc: elem, link: data[3][index]});
});
And a solution using map and filter might look like
data[1].map(function(elem, index) {
return ({
title: data[1][index],
description: data[2][index],
link: data[3][index]
});
}).filter(function(elem) {
return elem.description;
});
Looks like you need some lodash/underscore in your life…
http://underscorejs.org/
https://lodash.com/
They are libraries that do exactly that. Take Objects/Arrays and slice and dice them until they are what you want. Super useful.
Or you could just do it in plain ol' reduce…
var crazyArray = ['title here', ['one', 'two', 'three'], ['four', 'five'], '', ['six']]
var reduced = crazyArray.reduce(function( last, cur ){
if(cur) return last.concat(cur);
return last;
}, []);
console.log(reduced) // [ 'title here', 'one', 'two', 'three', 'four', 'five', 'six' ]