Jest mock function arguments don't match when using immutable data structures - javascript

When trying to test the arguments passed to the function with Jest's .toHaveBeenCalledWith() method, the test fails if I am treating with immutable data structures using ImmutableJS library. The test fails with the message similar to this:
Expected mock function to have been called with:
[{"foo": true, "bar": "baz"}]
But it was called with:
[{"foo": true, "bar": "baz"}]
The test looks similar to this:
const expectedArgs = Map({
foo: true,
bar: 'baz'
});
const foo = jest.fn();
bar();
expect(foo).toHaveBeenCalledWith(expectedArgs);
And the function similar to this:
const bar = () => {
const baz = Map({});
const bazModified = baz.set('foo', true).set('bar', 'baz');
foo(bazModified);
}
I realized that if I pass the arguments in this manner everything works fine:
const bar = () => {
const baz = Map({
foo: true,
bar: 'baz'
});
foo(baz);
}
The problem is that this is a big simplification of my function's logic and I have to use .set to construct the object. Does anybody have an idea why the approach with .set is failing to evaluate correctly?

So your test is failing because toHaveBeenCalledWith only passes if the instances of the entities are exactly the same. It is similar to the following, which also fails:
const expectedArgs = Map({
foo: true,
bar: 'baz'
});
const input = Map({
foo: false,
bar: 'baz'
});
const result = input.set('foo', true);
expect(result).toBe(expectedArgs);
This, on the other hand, does work:
expect(result).toEqual(expectedArgs);
Because that performs a deep equal.
There is no way you will be able to test equality with toHaveBeenCalledWith, because baz.set('foo', true) will always return a new instance (which is the point of using immutable data).
I don't think there is a way to make toHaveBeenCalledWith behave like toEqual, so I guess the way to go is to manually test the mock call with toEqual:
const expectedArgs = Map({
foo: true,
bar: 'baz'
});
const foo = jest.fn();
bar();
// foo.mock.calls[0][0] returns the first argument of the first call to foo
expect(foo.mock.calls[0][0]).toEqual(expectedArgs);
See the documentation on toBe, toEqual, and mock calls

Related

Spying on property changes with Sinon

I am stubbing properties with sinon. I have trouble understanding how the related spying works. Most of the sinon spy methods seem to be related to function calls and not to access of object properties. As a workaround I created a variable for tracking the property access and set it manually when the related getter function is called. See the code example below. This seems to work just fine but I feel like I am probably duplicating some functionality that already exists in sinon. What would be a more idiomatic way of achieving the same result?
const sinon = require('sinon');
const lib = require('./lib');
const app = require('./app');
const sandbox = sinon.createSandbox();
let fooAccessed = false;
sandbox.stub(lib, 'foo').get(() => {
fooAccessed = true;
return 123
});
app();
expect(fooAccessed).toEqual(true);
I know this is probably not the answer you want, because i do not use "sinon", but i will answer anyway. Perhaps this helps you in any way.
You can use a Proxy object to intercept accessing a object.
const obj = {
foo: "bar",
baz: true
};
let accessed = false;
const proxied = new Proxy(obj, {
get(target, prop) {
if (prop === "foo") {
accessed = true;
}
return target[prop];
}
});
console.log(proxied, accessed);
console.log(proxied.foo);
console.log(proxied, accessed);
Result:
{ foo: 'bar', baz: true } false
bar
{ foo: 'bar', baz: true } true

Create array with an additional property

I'm using an existing 3rd party API over which I have no control and which requires me to pass an array with an additional property. Something like the following:
type SomeArgument = string[] & { foo: string };
doSomething (argument: SomeArgument);
Now I find it quite clumsy and verbose, to create SomeArgument while keeping the compiler satisfied.
This one works, but no type safety at all:
const customArray: any = ['baz'];
customArray.foo = 'bar';
doSomething(customArray);
Another option which feels cleaner, but is quite verbose (I will need different subclasses with different properties) is to subclass Array:
class SomeArgumentImpl extends Array<string> {
constructor (public foo: string, content?: Array<string>) {
super(...content);
}
}
doSomething(new SomeArgumentImpl('bar', ['baz']));
Is there any better, one-liner-style way? I was hoping for something along doSomething({ ...['baz'], foo: 'bar' }); (this one does not work, obviously).
Suppose this is the function you want to call:
function doSomething (argument: string[] & { foo: string }) {
argument.push("a");
argument.foo = "4";
console.log(argument);
}
Then you can call it like this:
// Works
doSomething(Object.assign(["a", "b"], { foo: "c" }));
// Error
doSomething(Object.assign(["a", 2], { foo: "c" }));
// Error
doSomething(Object.assign(["a", 2], { foo: 4 }));

Destructure a function parameter subproperty

I've got a Function that I want to be able to call in 2 ways - and it should behave the same.
Is there any ES6 syntax that will allow me to call the function doBar below using both ways with the same result?
Consider a function like this:
const doBar = ({ foo = 'bar' }) => {
console.log(foo) // should log 'baz'
}
I'm using a framework that binds events like so:
<x-component on-some-event="doBar"></x-component>
which will essentially cause an invocation like so:
// where e = { detail: { foo: 'baz' } }
doBar(e)
.. but I'd like to be able to both call my Function explicitly as well, albeit with a proper call signature like so:
doBar({ foo: 'baz' })
You can use a default parameter. If foo is not found, it will use the value of detail.foo.
const doBar = ({ detail = {}, foo = detail.foo }) => {
console.log(foo) // should log 'baz'
}
doBar({ foo: 'baz' })
doBar({
detail: {
foo: 'baz'
}
});
You can't do this properly in the parameter declaration. Just put your destructuring assignment in the function body:
function doBar(e) {
const { foo = "bar", qux } = e.detail || e;
consoel.log(foo, qux);
}
I'd like to be able to both call my function explicitly as well
I would recommend to not do that. A function should have one signature only and not be overloaded to different types of arguments. Just be explicit about when to use what:
function doBar({ foo = "bar", qux }) {
console.log(foo);
}
function withDetail(fn) {
return e => fn(e.detail)
}
<x-component on-some-event="withDetail(doBar)"></x-component>
doBar({ foo: 'baz' })
No. Not unless you consider this to be an adequate alternative:
const thing = {
detail: {
foo: 'baz'
}
};
doBar(thing.detail);

Different results after creating two JS objects from (seemingly) identical JSON

I'll try to frame this in a framework-agnostic manner, but my use case is specific to react/redux/redux-form.
I first get a thing out of the store. This is important because if I define this object in place, things work fine. It makes it really difficult to reacreate the error, though. So, I'll go through a couple examples. The first one works, the rest don't. Let's say it has the following structure:
thing = {
foo: 'foo',
bar: ['bar'],
baz: 'baz'
}
This works fine
function mapStateToProps(state) {
const { thing } = state.thing;
// thing = {
// foo: 'foo',
// bar: ['bar'],
// baz: 'baz'
// }
// I don't care about `baz`, and I don't want to send it downstream
// If I hardcode this, everything works fine
// (except that my data is dynamic, so
// this is not a real solution)
const workingObj = { foo: 'foo', bar: ['bar'] }
return (
{ initialValues: workingObj }
);
}
This doesn't work, however
function mapStateToProps(state) {
const { thing } = state.thing;
const brokenObj = { foo: thing.foo, bar: thing.bar };
return (
{ initialValues: brokenObj }
);
}
I figured I was having some issue with references losing scope or something. Let's try a deep copy:
function mapStateToProps(state) {
const { thing } = state.thing;
const brokenObj = JSON.parse(JSON.stringify(
{ foo: thing.foo, bar: thing.bar }
);
return (
{ initialValues: brokenObj }
);
}
Here's that same one in more detail... I have no idea how workingObjStr and brokenObjStr could be parsed into anything other than identical objects.
function mapStateToProps(state) {
const { thing } = state.thing;
const workingObj = { foo: 'foo', bar: ['bar'] };
const brokenObj = { foo: thing.foo, bar: thing.bar };
const workingObjStr = JSON.stringify(workingObj);
const brokenObjStr = JSON.stringify(brokenObj);
console.debug(workingObjStr === brokenObjStr);
// => true
return (
{ initialValues: JSON.parse(brokenObjStr) }
);
}
So, my question in general terms is: how could two identical string objects produce different objects after being parsed? Might there be some optimization voodoo going on behind the scenes, or am I missing something fundamental? I can't imagine how anything downstream of this return would even know that these objects have been produced through different means, so I'm completely stumped.
I'll also note again that I can't really think of how you could reproduce this... it seems somehow tied to the redux environment, because if I define the thing object inside the function scope things work fine.
I'll also say that I know the initialValues object is properly formatted and is propagating downstream because I can see it inside of my form... but redux-form doesn't pick it up unless it is created manually inside of the mapStateToProps function scope. Again, I have no idea how it would even be able to tell the difference, but hopefully I'm just missing something silly...
Thanks,
Thomas

Has been called with object assertion

Function I'm spying on, receives object as an argument. I need to assert that the function been called with certain properties of the object.
e.g: my SUT has:
function kaboom() {
fn({
foo: 'foo',
bar: 'bar',
zap: function() { ... },
dap: true
});
}
and in my test I can do this:
fnStub = sinon.stub();
kaboom();
expect(fnStub).to.have.been.called;
and that works (it's good to know that fn's been called). Now I need to make sure that the right object has been passed into the function. I care about only foo and bar properties, i.e. I have to set match for specific properties of the argument. How?
upd: sinon.match() seems to work for simple objects. Let's raise the bar, shall we?
What if I want to include zap function into assertion? How do I make that work?
Assuming you're using sinon-chai, you can use calledWith along with sinon.match to achieve this
expect(fnStub).to.have.been.calledWith(sinon.match({
foo: 'foo',
bar: 'bar'
}));
To achieve called.with partial object check, you can also use chai-spies-augment package (https://www.npmjs.com/package/chai-spies-augment) :
Usage (please, notice to .default after importing):
const chaiSpies = require('chai-spies');
const chaiSpiesAugment = require('chai-spies-augment').default;
chai.use(chaiSpies);
chai.use(chaiSpiesAugment);
usage :
const myStub = { myFunc: () => true };
const spy1 = chai.spy.on(myStub, 'myFunc');
myStub.myFunc({ a: 1, b: 2 });
expect(spy1).to.have.been.called.with.objectContaining({ a: 1 });

Categories

Resources