Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[object Object] thrown when using mockRejectedValue() #7396

Closed
foxbunny opened this issue Nov 21, 2018 · 8 comments
Closed

[object Object] thrown when using mockRejectedValue() #7396

foxbunny opened this issue Nov 21, 2018 · 8 comments

Comments

@foxbunny
Copy link

foxbunny commented Nov 21, 2018

🐛 Bug Report

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.

To Reproduce

Steps to reproduce the behavior: n/a

Expected behavior

I expect that, regardless of where the mockRejectedValue() is called, the test would always succeed.

Link to repl or repo (highly encouraged)

I was not able to create a small test case for this as even in this project, mockRejectedValue() works as expected in most cases.

Run npx envinfo --preset jest

Cannot 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',
  ],
}
@rickhanlonii
Copy link
Member

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

@foxbunny
Copy link
Author

@rickhanlonii I figured out what it's doing. It's actually trapping an unhandled promise rejection. Is this by design?

@rickhanlonii
Copy link
Member

Not sure, @SimenB do you know?

@SimenB
Copy link
Member

SimenB commented Nov 27, 2018

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

@foxbunny
Copy link
Author

@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?

@SimenB
Copy link
Member

SimenB commented Nov 28, 2018

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.

@foxbunny
Copy link
Author

OK, thanks for your time and explanations. 👍

@github-actions
Copy link

This issue has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.
Please note this issue tracker is not a help forum. We recommend using StackOverflow or our discord channel for questions.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators May 12, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants