Jest function toHaveBeenCalledWith to ignore object order - javascript

Im testing with what arguments a function was called but its not passing because the order of properties inside an object, like:
const obj = {
name: 'Bla',
infos: {
info1: '1',
info2: '2'
}
}
expect(function).toHaveBeenCalledWith(obj)
The error says that was called like this: { name: 'bla', infos: {info2: '2', info1: '1'} }
I changed orders but didn't work.

You could follow a similar approach to this SO answer.
Example:
// Assuming some mock setup like this...
const mockFuncton = jest.fn();
const expectedObj = {
name: 'Bla',
infos: {
info1: '1',
info2: '2'
}
}
// Perform operation(s) being tested...
// Expect the function to have been called at least once
expect(mockFuncton).toHaveBeenCalled();
// Get the 1st argument from the mock function call
const functionArg = mockFuncton.mock.calls[0][0];
// Expect that argument matches the expected object
expect(functionArg).toMatchObject(expectedObj);
// Comparison using toEqual() instead which might be a better approach for your usecase
expect(functionArg).toEqual(expectedObj);
Expect.toMatchObject() Docs
Expect.toEqual() Docs

it('does not care on properties ordering', () => {
const a = jest.fn();
a({ a: 1, b: 2, c: {d1: 1, d2: 2} });
expect(a).toHaveBeenCalledWith({c: {d2: 2, d1: 1}, b: 2, a: 1});
});
passes for me with Jest 24.9.0
Under the hood, Jest applies "isEqual" check, not referential check
But we cannot check for functions equality this way. Also partial matching will need custom matcher.

Related

Compare response in jest unit test case

I'm new to jest unit test case scenario, I have a scenario where in the response from the service that I called is of the below format
Artifact {
name: 'detection-v1.zip',
file_path: 'artifact\\bn-ds-anomalydetection-v1.zip',
is_tenant: false,
metadata: [
Registerfact {
name: 'ad',
_meta: [Object],
line_meta: [Object]
},
Registerfact {
name: 'ad-generic',
_meta: [Object],
line_meta: [Object]
}
]
}
how can i compare the above response in the jest service , I was trying to create a object but the Artifact name before the object is confusing how should i proceed
The test case is
test('test processArtifact method', async()=>{
const mockGetRestClient = jest.fn();
try{
const response = await factService.processfact(artifact)
console.log("response---",response)
// expect(response).toEqual()
}
catch(e){ }
})
I know its silly question ,but i'm confused hence posted it.
How should i create the static object to be put in .toEqual() ?
You can declare a global/static var with your response object on top of file. Or better declare it in some constants file and import here.
For Comparison:
Usually, if you have a simple object, you can use JSON.stringify. However, it may give error due to different order of object keys.
You should use assert for the deep comparison. There is method assert.deepEqual() which does deep comparison of objects.
an example for using assert from official docs
import assert from 'node:assert';
const obj1 = {
a: {
b: 1
}
};
const obj2 = {
a: {
b: 2
}
};
const obj3 = {
a: {
b: 1
}
};
const obj4 = Object.create(obj1);
assert.deepEqual(obj1, obj1);
// OK
// Values of b are different:
assert.deepEqual(obj1, obj2);
// AssertionError: { a: { b: 1 } } deepEqual { a: { b: 2 } }
assert.deepEqual(obj1, obj3);
// OK
// Prototypes are ignored:
assert.deepEqual(obj1, obj4);
// AssertionError: { a: { b: 1 } } deepEqual {}
Hope this helps, let me know if you have any questions.
You can use JSON.stringify in order to convert your object into a string and then compare this result to the one you expect.
console.log(JSON.stringify({
name: 'detection-v1.zip',
file_path: 'artifact\\bn-ds-anomalydetection-v1.zip',
is_tenant: false,
metadata: [
{Registerfact: {
name: 'ad',
_meta: {},
line_meta: {}
}},
{Registerfact: {
name: 'ad-generic',
_meta: {},
line_meta: {}
}}
]
}));

Array filtering in nested object using ramda

Let's assume that we have the following object:
const sample = {
foo: {
tags: [
'aaa', 'bbb'
],
a: 1,
b: 10
},
bar: {
tags: [
'ccc', 'ddd'
],
a: 11,
b: 100
}
}
How can one remove a specific tag value from object sample using ramda?
I have done this
/// Remove tag named 'aaa'
R.map(v => R.assoc('tags', R.without('aaa', v.tags), v), sample)
which achieves the desired result but how can I eliminate the lamda (and the closure created) inside map ?
You could use evolve instead of assoc. assoc expects a property and plain value to set on provided object, whereas evolves expects a property and function producing the new value (although in a slightly other syntax).
R.map(R.evolve({tags: R.without('aaa')}), sample)
You can R.evolve each object, and use R.without to transform the value of tags:
const { map, evolve, without } = R
const fn = map(evolve({
tags: without('aaa')
}))
const sample = {"foo":{"tags":["aaa","bbb"],"a":1,"b":10},"bar":{"tags":["ccc","ddd"],"a":11,"b":100}}
const result = fn(sample)
console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.27.0/ramda.js"></script>

Turf JS does not create Object properly

I am using the helper function turf.point()
const feature = turfHelpers.point(coords, properties, { id: properties.id });
properties looks like this
properties = {
id: 1,
thisWorks: 'no problem'
foo: {
thisDoesntWork: 'this is a problem'
}
}
When I create feature with turfHelpers.point(), it messes with the object. The nested object is not an object anymore, but gets stringyfied...
So, features.properties is
{
id: 1,
thisWorks: 'no problem'
foo: "{
thisDoesntWork: 'this is a problem'
}"
}
Now, I cannot access. feature.properties.foo.thisDoesntWork anymore, because its a string...
Why is turf.js doing that?
Let's put the question in the runnable form.
const turfHelpers = turf.helpers;
const coords = [100, 14];
const properties = {
id: 1,
thisWorks: 'no problem',
foo: {
thisDoesntWork: 'this is a problem'
}
};
var feature1 = turfHelpers.point(coords, properties, {
id: properties.id
});
// Print out on console
console.log(feature1.properties); //look OK
console.log(feature1.properties.foo); //also OK
console.log(feature1.properties.foo.thisDoesntWork); //also OK
<script src="https://cdnjs.cloudflare.com/ajax/libs/Turf.js/5.1.5/turf.min.js"></script>
Then, hopefully, it is helpful for discussion that leads to a solution.

ES6 Default Parameters in nested objects

I want to have a function with default parameters inside nested objects, and I want to be able to call it either f() or specifying only individual parameters.
// A function with nested objects with default parameters:
function f({ a = 1, callback = ({ name, param } = { name: "qwe", param: 123 }) } = {}) {
console.log("a:", a);
console.log("callback:", callback);
}
// And I want to run it like this:
f();
f({ callback: { params: "456" } });
// But 'callback.name' becomes undefined.
When destructuring is mixed with default parameters, I admit the code is hard to read and write (especially when there are nested objects...).
But I think you are trying to do that:
function f({callback: {name = "cbFunction", params = "123"} = {}} = {}) {
console.log(name);
console.log(params);
}
f();
f({callback: {params: '789'}});
I found none of the answers here to be what he wanted. But it IS actually possible in a somewhat sexy way by doing this:
(EDIT: Simplified syntax and also show how to add default values for subobjects)
function f({
a = 1,
callback = {}
} = {}) {
callback = { // default values
name: "cbFunction",
params: "123",
...callback // overwrites it with given values
}
// do the same for any subobjects
callback.subObject = {
arg1: 'hi',
arg2: 'hello',
...callback.subObject
}
console.log("a:", a)
console.log("callback:", callback)
}
f()
f({a: 2, callback: {params: '789', subObject: {arg2: 'goodbye'}}})
Turned out to call it like this solves the problem, but is it the best way?
function f({
a = 1,
callback = ({
name,
param
} = {
name: "qwe",
param: 123
})
} = {}) {
console.log("a:", a);
console.log("callback:", callback);
}
f();
f({ callback: { name, params: "456" } });
Answer by #Badacadabra is nearly correct but missing the other top level parameter specified in the question.
function f({a = 1, callback: {name = "qwe", params = "123"} = {}} = {}) {
console.log(a);
console.log(name);
console.log(params);
}
However note that within the function body the properties of callback are addressed without the containing object. You could reconstitute them into such an object if you wanted with the line:
const callback = { name, params }
Either way, from the invocation point this works to fill in all missing values from all levels such as:
f({a: 2})
f({a: 2, callback: { name: "abc"}})
f({a: 2, callback: { params: "456" }})
etc.
EDIT
In response to Joakim's comment:
TotalAMD also said in a comment that "I want to use several nested objects with same fields name". So if he tries that approach with callback1 and callback2 as arguments then he would have to use different field names in them.
I missed that original requirement. One way to maintain the desired, duplicated nested names within the function interface would be to alias them within the scope of the function, as follows:
function f({
a = 1,
callback1: {name: name1 = "abc", params: params1 = "123"} = {},
callback2: {name: name2 = "def", params: params2 = "456"} = {},
} = {}) {
console.log(a);
console.log(name1);
console.log(params1);
console.log(name2);
console.log(params2);
}
You can then call the function with the designed interface and expected results:
f ({ callback1: { name: "One" }, callback2: { name: "Two" } })
Caveat: Whilst technically possible and potentially useful, this could get messy at deeper nesting levels. It might then be worth looking for an alternative interface design with less indirection.

Chai.js: Object contains/includes

Chai has an include method. I want to test to see if an object contains another object. For example:
var origin = {
name: "John",
otherObj: {
title: "Example"
}
}
I want to use Chai to test if this object contains the following (which it does)
var match = {
otherObj: {
title: "Example"
}
}
Doing this does not appear to work:
origin.should.include(match)
Hei, just published chai-subset. Check this out: https://www.npmjs.org/package/chai-subset
This should work for you)
var chai = require('chai');
var chaiSubset = require('chai-subset');
chai.use(chaiSubset);
var obj = {
a: 'b',
c: 'd',
e: {
foo: 'bar',
baz: {
qux: 'quux'
}
}
};
expect(obj).to.containSubset({
e: {
foo: 'bar',
baz: {
qux: 'quux'
}
}
});
The include and contain assertions can be used as either property based language chains or as methods to assert the inclusion of an object in an array or a substring in a string. When used as language chains, they toggle the contain flag for the keys assertion. [emphasis mine]
So if you're invoking include on an object (not an array or a string), then it only serves to toggle the contain flag for the keys assertion. By the looks of your example, testing for deep equality would make more sense, possibly checking for the key first.
origins.should.include.keys("otherObj");
origins.otherObj.should.deep.equal(match.otherObj);
Actually, now I browse the other examples, you would probably be happiest with this :
origins.should.have.deep.property("otherObj", match.otherObj)
In chai 4.2.0, for example you can use deep include
chaijs doc examples:
// Target array deeply (but not strictly) includes `{a: 1}`
expect([{a: 1}]).to.deep.include({a: 1});
expect([{a: 1}]).to.not.include({a: 1});
// Target object deeply (but not strictly) includes `x: {a: 1}`
expect({x: {a: 1}}).to.deep.include({x: {a: 1}});
expect({x: {a: 1}}).to.not.include({x: {a: 1}});
If you know the level of the subobject you can simply use:
expect(origin.otherObj).to.include(match.otherObj);
https://www.chaijs.com/api/bdd/
In Chai 1.5.0 you will find handy method
includeDeepMembers
http://chaijs.com/releases/

Categories