Using .toMatchObject()
returns failing test with message Received: serializes to the same string
I am trying to check the users
object I receive against my expectedUsers
. The received object coming back from MongoDB contains the fields "__v"
and "_id"
which I do not want to check for (they always change for every test). As such, I am using .toMatchObject()
and cannot use something else like .toEqual()
. My test snippet is below:
test("should show all existing users", async () => {
const expectedUsers = [
{
email: "[email protected]",
friends: [],
followers: [],
following: [],
blocked: []
},
{
email: "[email protected]",
friends: ["[email protected]", "[email protected]"],
followers: [],
following: [],
blocked: []
},
{
email: "[email protected]",
friends: [],
followers: [],
following: [],
blocked: ["[email protected]"]
}
];
await request(app)
.get(route(path))
.expect("Content-Type", /json/)
.expect(200);
const users = await User.find();
expect(users).toMatchObject(expectedUsers);
});
(request
is made with supertest
)
As documented here,
Use .toMatchObject to check that a JavaScript object matches a subset of the properties of an object. It will match received objects with properties that are not in the expected object.
Since the expected
objects is a subset of received
objects, I expect my test to pass.
npx envinfo --preset jest
resultSystem:
OS: macOS 10.14.4
CPU: (12) x64 Intel(R) Core(TM) i9-8950HK CPU @ 2.90GHz
Binaries:
Node: 10.15.2 - ~/.asdf/shims/node
npm: 6.9.0 - ~/.asdf/shims/npm
npmPackages:
jest: ^24.8.0 => 24.8.0
cc @pedrottimark
@sabriele Yes, your choice of toMatchObject
makes sense. So we can trouble shoot:
console.log(users)
mongoose
@sabriele From reading Jest code and guessing about MongoDB, users
array might have non-index properties which toMatchObject
should (but does not) ignore.
Here is a work-around to get rid of them:
expect([...users]).toMatchObject(expectedUsers)
If you can paste the received users
before work-around, we can make a realistic regression test.
I have the same issue. Here is the test for a react custom hook:
import {renderHook} from 'react-hooks-testing-library'
import useTheme, {DEFAULT_THEME} from 'components/globalStyle/useTheme'
it('should set the global theme', () => {
const setTheme = () => {}
const expected = {...DEFAULT_THEME, setTheme}
const {result} = renderHook(useTheme)
expect(result.current).toMatchObject(expected)
})
This test returns the following error:
Error: expect(received).toMatchObject(expected)
Expected: {"palette": "dark", "setTheme": [Function setTheme], "textSize": "normal"}
Received: serializes to the same string
I tried the shallow copy trick that @pedrottimark suggested but it didn't work (same error). However, the following seems to work just fine:
import {renderHook} from 'react-hooks-testing-library'
import useTheme, {DEFAULT_THEME} from 'components/globalStyle/useTheme'
it('should set the global theme', () => {
const Mock = jest.fn()
const setTheme = new Mock()
const {result} = renderHook(useTheme)
const expected = {...DEFAULT_THEME, setTheme}
expect(result.current).toMatchObject(expected)
})
Setting const setTheme = jest.fn()
didn't work 🤷♂️
Error: expect(received).toMatchObject(expected)
- Expected
+ Received
Object {
"palette": "dark",
- "setTheme": [Function mockConstructor],
+ "setTheme": [Function setTheme],
"textSize": "normal",
}
@matchatype If the problem in your https://github.com/facebook/jest/issues/8475#issuecomment-495943549 is like #8166 that deep-equality matchers compare functions according to referential identity, then we recommend asymmetric matcher as expected value, see https://jestjs.io/docs/en/expect#expectanyconstructor
it('should set the global theme', () => {
const setTheme = expect.any(Function)
const expected = {...DEFAULT_THEME, setTheme}
const {result} = renderHook(useTheme)
expect(result.current).toMatchObject(expected)
})
That does indeed work! However, I'm still confused: all examples should result in the same behavior. Instead, each triggers a completely different response:
The recent change to display serializes to the same string
makes more obvious when there are inconsistencies between the comparison in the matcher and the feedback in the report.
@matchatype In the case that you describe:
() => {}
or jest.fn()
as expected value are not referentially equal to (that is, not the same instance as) the function returned by the hookDeep-equality matchers compare different instances of functions:
Symbol()
is not equal to Symbol()
[0]
is equal to [0]
or {key: 'value'}
is equal to {key: 'value'}
If you think of the returned data structure as a tree, there is a difference between asserting a primitive value as a leaf, and asserting a function or symbol (when the caller does not provide it as an argument).
I am not sure why the work-around that you found solves the problem :)
A long-term goal for Jest is to bridge gaps like this between the comparison and the report.
serializes to the same string
is symptom of a different problem in the original https://github.com/facebook/jest/issues/8475#issue-446046819
toMatchObject
matcher compares non-index properties (that is, symbols or non-numeric strings) of arrays same as toEqual
matcher, instead of being able to ignore properties according to expected subsetgetObjectSubset
helper ignores non-index properties in received value for report, even if they were in the expected subsetpretty-format
package ignores non-index properties, even if getObjectSubset
included themThe difficulty to solve those problems: is 2. medium, 1. difficult, 3. breaking
Extremely helpful @pedrottimark Many thanks 🙏Yes, the fact that work-around actually passed totally baffled me.
I have the same problem, for me the problem comes from the function I have in the object.
The solution for me is to mock function by jest.fn()
and put it to input props and expected object.
toEqual in jest can compare two object, it is cool (in js we can't compare directly by '=='), but if the object contains an function (like () => {}), it will have problem to compare.
Hi @pedrottimark, I apologise for the tardy reply; this was a weekend project and I simply got swamped with work.
Yes, I am using mongoose; I did a diff on the result of console.log(users)
and console.log([...users])
and they are exactly the same:
[ { friends: [],
followers: [],
following: [],
blocked: [],
_id: 5cfbb57e37912c8ff6d2f8b1,
email: '[email protected]',
__v: 0 },
{ friends:
[ '[email protected]', '[email protected]' ],
followers: [],
following: [],
blocked: [],
_id: 5cfbb57e37912c8ff6d2f8b2,
email: '[email protected]',
__v: 0 },
{ friends: [],
followers: [],
following: [],
blocked: [ '[email protected]' ],
_id: 5cfbb57e37912c8ff6d2f8b3,
email: '[email protected]',
__v: 0 } ]
Just like @matchatype I too tried the shallow copy trick but it gave me the same error.
Thank you for trying to help me troubleshoot this! I really appreciate it.
@sabriele Thank you for the output. When I copy and paste into a local test file, there is syntax error for values of _id
properties like 5cfbb57e37912c8ff6d2f8b1
instead of '5cfbb57e37912c8ff6d2f8b1'
That confirms mongoose provides some methods on user
object instances.
EDIT: That is, a method that somehow “improved” the default output from console.log
If shallow copy of the array did not help, then the next step is something like:
expect(users.map(user => user.toObject())).toMatchObject(expectedUsers);
See https://mongoosejs.com/docs/api.html#document_Document-toObject
Converts this document into a plain javascript object, ready for storage in MongoDB.
If that is a solution, then I will have some follow-up questions to understand what is the problem.
The toObject works for me
@patran So I can understand the problem in toMatchObject
if your test gets an array of objects from MongoDB with mongoose, can you add console.log(…)
for original array and first object:
Object.getOwnPropertyDescriptors(array)
copy and then delete array index propertiesObject.getOwnPropertyDescriptors(array[0])
copy and then delete properties of the dataPaste the results after editing to delete properties that are not added by mongoose. Thank you!
Have same problem
I have similar problem comparing Buffers.
expect(a).toEqual(b)
throws "serializes to the same string"
expect(a.equals(b)).toBe(true)
works fine
I have tried to find any difference between these objects using Object.getOwnPropertyDescriptors
, but looks like they are the same.
I run into the "serializes to the same string" issue when using toMatchObject. The objects had functions defined and was the reason toMatchObject failed. I worked around the issue by mocking them:
const mockFunctions = <T extends Record<string, any>>(obj: T, mock: any): T => {
const copy = { ...obj };
Reflect.ownKeys(copy)
.filter(key => typeof Reflect.get(copy, key) === "function")
.forEach(key => Reflect.set(copy, key, mock));
return copy;
};
For toMatchObject to work as expected it was important to use the same jest mock on both objects.
const objectToCompare = (name: string) => {
const nameAsFunc = (): string => name;
return {
name,
nameAsFunc
};
};
describe("toMatchObject tests", () => {
it("can compare objects with functions", () => {
const mock = jest.fn();
const first = objectToCompare("name");
const second = objectToCompare("name");
// Gives "serializes to the same string"
expect(first).toMatchObject(second);
// Works
expect(mockFunctions(first, mock)).toMatchObject(mockFunctions(second, mock));
});
});
@matchatype If the problem in your #8475 (comment) is like #8166 that deep-equality matchers compare functions according to referential identity, then we recommend asymmetric matcher as expected value, see https://jestjs.io/docs/en/expect#expectanyconstructor
it('should set the global theme', () => { const setTheme = expect.any(Function) const expected = {...DEFAULT_THEME, setTheme} const {result} = renderHook(useTheme) expect(result.current).toMatchObject(expected) })
That worked for me too. Thanks !
I'm also experiencing this issue. This is my workaround:
expect(JSON.stringify(result.current)).toEqual(JSON.stringify(expected));
@manhhailua Thank you so much! This worked for me after hours of agony.
I'm also experiencing this issue. This is my workaround:
expect(JSON.stringify(result.current)).toEqual(JSON.stringify(expected));
@pedrottimark Are you guys planning to fix this any time soon? I am also using shallow rendering and experience bad test results. 😕
Here is my test code:
expect(shallowResult.props.children).toEqual(
[<Todo todo={fakeTodosData.data[0]} />,
<Todo todo={fakeTodosData.data[1]} />]
);
When shallowResult.props.children
is the correct thing my test outs this:
Expected: [<Todo todo={{"description": "", "id": 100, "title": "Text!"}} />, <Todo todo={{"description": "More text...", "id": 42, "title": "Other Text"}} />]
Received: serializes to the same string
^ (horrible output and really should be changed)
When I change the matcher to "toContainEqual" is outputs this:
Expected value: [<Todo todo={{"description": "", "id": 100, "title": "Text!"}} />, <Todo todo={{"description": "More text...", "id": 42, "title": "Other Text"}} />]
Received array: [<Todo todo={{"description": "", "id": 100, "title": "Text!"}} />, <Todo todo={{"description": "More text...", "id": 42, "title": "Other Text"}} />]
(^ a failing test showing that the results are exactly the same. This is _super confusing_ and it also should really be changed)
Even using the "stringify-all-the-things" hack from @manhhailua does not work for me. It seems that the "key" field that is necessary when rendering components in a loop is hidden away in the test output. Here is my stringified test failure:
Expected: "[{\"key\":null,\"ref\":null,\"props\":{\"todo\":{\"id\":100,\"title\":\"Text!\",\"description\":\"\"}},\"_owner\":null,\"_store\":{}},{\"key\":null,\"ref\":null,\"props\":{\"todo\":{\"id\":42,\"title\":\"Other Text\",\"description\":\"More text...\"}},\"_owner\":null,\"_store\":{}}]"
Received: "[{\"key\":\"key0\",\"ref\":null,\"props\":{\"todo\":{\"id\":100,\"title\":\"Text!\",\"description\":\"\"}},\"_owner\":null,\"_store\":{}},{\"key\":\"key1\",\"ref\":null,\"props\":{\"todo\":{\"id\":42,\"title\":\"Other Text\",\"description\":\"More text...\"}},\"_owner\":null,\"_store\":{}}]"
@pedrottimark Are you the maintainer of this 'react-test-renderer/shallow' project? I would very much like this to be fixed, and I have bandwidth to work on this right now if you need help. This is extremely disappointing to me as I do very much like the way 'react-test-renderer/shallow' works (much nicer than enzyme imo). It would be even nicer though if it gave more insight into why the tests are not passing! 😄
Thanks!
In the end my test is passing with this (I was forgetting the "key" field and wasn't aware it was missing until doing the stringified comparison):
expect(JSON.stringify(shallowResult.props.children)).toEqual(
JSON.stringify(
[<Todo todo={fakeTodosData.data[0]} key={'key0'}/>,
<Todo todo={fakeTodosData.data[1]} key={'key1'}/>]
)
);
fyi, swapping .toBe
to .toEqual
helped my case:)
Circular-structured JSONs proof:
// Workaround when Using .toMatchObject() returns failing test with message Received: serializes to the same string
// https://github.com/facebook/jest/issues/8475
const getCircularReplacer = () => {
const seen = new WeakSet();
return (key, value) => {
if (typeof value === 'object' && value !== null) {
if (seen.has(value)) {
return;
}
seen.add(value);
}
return value;
};
};
const isStringifiedComparisonEqual = (a: Object, b: Object): boolean =>
JSON.stringify(a, getCircularReplacer()) === JSON.stringify(b, getCircularReplacer());
expect(isStringifiedComparisonEqual(objectA, objectB));
In my case I had:
const obj = { value: 'value', array: ['array'] };
expect(obj).toMatchObject({ ... });
And got the error, but was able to resolve that, by wrapping nested array with expect.arrayContaining(['array'])
(inside toMatchObject
). It is because Jest probably doesn't resolve nested array automatically in that case.
jumping onto this thread, when an object contains methods I run into this:
const a = {
getSomething: () => ({
getSomethingElse: () => ({
something: 'I want',
}),
}),
};
const b = {
getSomething: () => ({
getSomethingElse: () => ({
something: 'I want',
}),
}),
};
expect(a).toMatchObject(b); // _Expected: {"getSomething": [Function getSomething]} Received: serializes to the same string_
Hello. Sorry if I missed some message that was describing the issue already, but I've created a sandbox with reproduction for you:
https://codesandbox.io/s/nameless-violet-vk4gn
See the src/index.test.js
source and "Tests" tab for the results
@pedrottimark
Here is a work-around to get rid of [non-index properties]:
expect([...users]).toMatchObject(expectedUsers)
users.slice(0)
also gets rid of non-index properties. Might it be faster?
users.slice(0)
also gets rid of non-index properties. Might it be faster?
The difference is very minor https://jsperf.com/slice-vs-spread-2
Is there a way to disable "serializes to the same string" so it could resolve positively? Maybe additional configuration for Jest? Quite annoying that we have to look for a workaround every time we need to compare deep nested objects, object methods, etc.
thx @manhhailua
@DnEgorWeb to achieve this functionality you could serialize the objects yourself and compare the results. In my use case this behavior is a good thing because I need to make sure the objects are actually the same all the way through
I had this problem too but I found I could wrap an expect
inside of an expect
and catch the throw error:
expect(() => {
expect(toReact(children)).toEqual([
"Hello, ",
<A href="https://google.com">
world
</A>,
"!",
])
}).toThrow("serializes to the same string")
I hope this helps someone. By the way you can actually test the throw message using regex: https://jestjs.io/docs/en/expect#tothrowerror.
Maybe this will help somebody else. I had this error after introducing a circular dependency while writing tests. Removing the circular dependency resolved the issue.
Most helpful comment
I'm also experiencing this issue. This is my workaround: