In some cases (I was not able to determine when and why), jest will fail a test with a message that looks like this:
[object Object] thrown
In one of the tests, I was able to make it pass by changing the location of the mock:
it('should show an error message on failure', async () => {
// <-- from here
const vm = createDialog({ list, close: jest.fn() })
const action = createActionInterface(vm)
action.enterText('input', 'Bar')
await nextTick()
data.renameList.mockRejectedValue({ status: 500 }) // <--- to here
action.submitForm('form')
await nextTick()
expect(vm.$options.propsData.close).not.toHaveBeenCalled()
expect(vm.$el.querySelector('.Message')).toMatchSnapshot()
})
In the above example (using Vue), action.submitForm('form') will force the submit event on a form and that triggers a call to data.renameList() which is mocked here.
In another test, I ran into the same issue:
it('should show an error message on server error', async () => {
const { vm, action } = bootstrap(PasswordChange)
vm.$mount()
action.enterText('[name="current"]', 'my old pw 123')
action.leaveField('[name="current"]')
await nextTick()
action.enterText('[name="password"]', 'my new pw 123')
action.leaveField('[name="password"]')
await nextTick()
action.enterText('[name="repeat"]', 'my new pw 123')
action.leaveField('[name="repeat"]')
await nextTick()
data.changePassword.mockRejectedValue({ status: 500 })
action.submitForm('form')
await nextTick() // <-- throws around here but cannot be caught
expect(vm.$el).toMatchSnapshot()
})
Moving the mock to the line just above form submission does not work in this case. I've marked the line that I suspect leads to the exception based on what wallaby's coverage reports say. The exception cannot be trapped in any way from either the test code or the application code (I've tried putting try catch around the whole test to no avail.
The await nextTick() is function that wraps setTimeout() in a Promise to allow Vue to re-render the interface after state change.
Point of failure (according to wallaby) depends on where the mockRejectedValue() is called:
it('should show an error message on server error', async () => {
data.changePassword.mockRejectedValue({ status: 500 }) // <-- at the top
const { vm, action } = bootstrap(PasswordChange)
vm.$mount()
action.enterText('[name="current"]', 'my old pw 123')
action.leaveField('[name="current"]') // <-- fails here
await nextTick()
action.enterText('[name="password"]', 'my new pw 123')
action.leaveField('[name="password"]')
await nextTick()
action.enterText('[name="repeat"]', 'my new pw 123')
action.leaveField('[name="repeat"]')
await nextTick()
action.submitForm('form')
await nextTick()
expect(vm.$el).toMatchSnapshot()
})
In the second case, the code that is invoked as a result of action.leaveField() is completely unrelated to the mocked function.
In all cases, there are multiple calls to mockResolvedValue() in at least one of the previous tests, and there is a call to mockClear() made before each test.
Steps to reproduce the behavior: n/a
I expect that, regardless of where the mockRejectedValue() is called, the test would always succeed.
I was not able to create a small test case for this as even in this project, mockRejectedValue() works as expected in most cases.
npx envinfo --preset jestCannot install envinfo
OS: Windows 10 64bit
NodeJS: v8.11.1
NPM: v5.6.0
package.json deps:
"babel-core": "^6.26.0",
"babel-jest": "^22.4.3",
"babel-loader": "^7.1.4",
"babel-plugin-add-module-exports": "^0.2.1",
"babel-plugin-dynamic-import-webpack": "^1.0.2",
"babel-plugin-ramda": "^1.6.1",
"babel-plugin-syntax-dynamic-import": "^6.18.0",
"babel-plugin-transform-class-properties": "^6.24.1",
"babel-plugin-transform-decorators-legacy": "^1.3.4",
"babel-plugin-transform-object-rest-spread": "^6.26.0",
"babel-plugin-transform-react-jsx": "^6.24.1",
"babel-preset-env": "^1.6.0",
"babel-preset-jest": "^21.0.2",
"jest": "^22.4.3",
babelrc:
{
"presets": [
[
"env",
{
"targets": {
"browsers": [
"last 2 major versions",
"ie >= 11"
]
},
"useBuiltins": true
}
]
],
"plugins": [
"ramda",
"syntax-dynamic-import",
"dynamic-import-webpack",
"transform-decorators-legacy",
"transform-object-rest-spread",
"transform-class-properties",
[
"transform-react-jsx",
{
"pragma": "h"
}
]
]
}
jest.config.js:
module.exports = {
verbose: true,
testURL: 'http://localhost/',
transform: {
'\\.js': 'babel-jest',
},
testRegex: '\\.test\\.(js|ts|tsx)$',
modulePaths: [
'<rootDir>',
'<rootDir>/src/',
'<rootDir>/node_modules/',
],
moduleFileExtensions: [
'js',
],
moduleNameMapper: {
'(\\.scss|\\.webfont)': 'identity-obj-proxy',
'(\\.handlebars)': '<rootDir>/src/__mocks__/hbs.js',
'\\.(jpg|png|gif|ttf|woff|eot|svg|html)': '<rootDir>/src/__mocks__/file-mock.js',
},
setupFiles: [
'./src/__testing__/setup.js',
],
}
Hey @foxbunny, thanks for using Jest and for the detailed report!
If I were to guess, something in your app code is throwing when you don't expect it to (I've dealt with this a lot when dealing with generators, it's super frustrating)
If you can trim this down to something reproducible that shows it's a bug in jest I'm happy to take a look and re-open
@rickhanlonii I figured out what it's doing. It's actually trapping an unhandled promise rejection. Is this by design?
Not sure, @SimenB do you know?
If you don't handle the rejection (a catch somewhere, or awaiting it/returning it to jest), then yes, it's expected.
[object Object] is since you're on Jest 22, please upgrade to 23, which includes #5980
@SimenB Thanks. I've upgraded to 23 meanwhile, and indeed, as you say, it shows the object being thrown.
Is it considered an anti-pattern to let promise rejections go unhandled?
Is it considered an anti-pattern to let promise rejections go unhandled?
Yes. Node itself will crash in the future when you do it, similar to uncaught errors.
OK, thanks for your time and explanations. 馃憤
Most helpful comment
If you don't handle the rejection (a
catchsomewhere, orawaiting it/returning it to jest), then yes, it's expected.[object Object]is since you're on Jest 22, please upgrade to 23, which includes #5980