Bugsnag-js: Unable to run Jest unit tests when ErrorBoundary is imported

Created on 3 May 2018  ยท  12Comments  ยท  Source: bugsnag/bugsnag-js

Expected behavior

I'm trying to add more granular error boundaries, rather than merely having a single wrapper around the app root. This is per React 16 intention to allow for catching errors without unmounting the entire app.

Observed behavior

It works fine in browser, but my Jest test suite won't run when bugsnag client is imported.

This error is output by Jest test runner:

  โ— Test suite failed to run

    TypeError: (0 , _bugsnagJs2.default) is not a function

      at Object.<anonymous> (src/app/initializers/bugsnag.js:17:43)
      at Object.<anonymous> (src/app/components/ErrorBoundary/ErrorBoundary.jsx:38:16)
      at Object.<anonymous> (src/app/components/ErrorBoundary/index.js:7:22)

  console.error node_modules/bugsnag-js/node/index.js:1
    +-------------------------------------------------------------+
      Bugsnag Error: "bugsnag-js" is for browser JavaScript only.
      For node, use the "bugsnag" package.
    +-------------------------------------------------------------+``

Steps to reproduce

Import ErrorBoundary into any component with a unit test.

Version

1.1

Additional information

[Insert any additional information]

Can't comment on Issues?

Some users have been unable to comment on Github issues when an adblocker extension is enabled.
We recommend temporarily disabling the extension, or if that fails, contacting [email protected].

Most helpful comment

To those that come across this ticket in the future, the following worked for me:

jest.mock('bugsnag-js', () => (
  () => ({
    use(plugin) {
      const boundary = plugin.init();
      delete boundary.prototype.componentDidCatch;
      return boundary;
    },
  })
));

It is a slightly modified version of the mock provided above, but it returns a function instead of an object

@dbchristopher

All 12 comments

Hmm, sorry about that, bugsnag-js needs to run in a browser environment and jest runs tests in a node environment. We should figure out a recommended workaround, but in the mean time one thing you could try is to stub the error boundary in your jest tests to render a dumb pass through component. I think the syntax would be

jest.mock('src/app/components/ErrorBoundary', () => ({children}) => children)

You can also add this to your global setup file to have it run before every test.

If you don't want to mock the error boundary you can mock the bugsnag-js module itself but that will require a more complicated mock object.
I haven't tested this but I think it would work

jest.mock('bugsnag-js', () => ({
  use (plugin) {
     const boundary = plugin.init()
     // we don't want the error boundary to swallow the errors, we want jest see them and fail the test
     delete boundary.prototype.componentDidCatch;
     return boundary;
  }
}))

Thank you for the response @wordofchristian. I'll play with mocking that out and see how far I get. Much appreciated!

@wordofchristian the bugsnag-js mock definitely gets me closer! At least it doesn't throw the error about running in a node environment. I'm still getting this import error though:

TypeError: (0 , _bugsnagJs2.default) is not a function

      at Object.<anonymous> (src/app/initializers/bugsnag.js:17:43)
      at Object.<anonymous> (src/app/components/ErrorBoundary/ErrorBoundary.jsx:22:16)
      at Object.<anonymous> (src/app/components/ErrorBoundary/index.js:7:22)

It seems like it want's a named import instead of a default import somewhere. I haven't been able to figure out why it can find the correct import in browser but not in jest. Have you come across anything like that before?

I'm assuming that src/app/initializers/bugsnag.js is just configuring the bugsnag client and re-exporting it. You might try mocking that instead of the bugsnag-js module.

I'm not sure why it's saying default is not defined, it seems like it could be some sort of babel setting, but mocking your initializer would likely make it go away.

It's not a function because there is nothing exported by the file that node loads:
https://github.com/bugsnag/bugsnag-js/blob/master/node/index.js

It it were helpful, we could add a default export to this file which is a noop? And optionally that could have the side-effect of logging a similar warning?

The best thing I can recommend is not loading bugsnag at all in your tests. I think as @wordofchristian says, mocking your initializer instead of bugsnag should get that job done.

In future when we have a "universal" notifier (one that will run in the browser and in node), we won't need to bail like we do at the moment when the browser notifier detects it's running in node.

Thanks @bengourley! I do think at least the noop default export would be great. I know the intent for ErrorBoundary is that it could be used at higher granularity, to preserve more of the app chrome in a fail state. So it's definitely frustrating to feel blocked by a key React 16 feature in order to integrate with bugsnag. But for now mocking our own internal imports gets the job done, if not totally ideal.

I think from a philosophical standpoint the error boundary behavior is something that you only want in a production environment. In tests it's better to have the app blow up so the test fails with the original error stacktrace and determining the cause of the problem is more obvious. Otherwise if the error boundary captures the error the test suite would only fail from not finding an element that it was expecting to find, or worse yet not fail at all.

@wordofchristian I should be able to use ErrorBoundary within a component, and have component unit tests still run, correct? It wouldn't be good practice to have a component render different children depending on the environment, so I have to strongly disagree with you there!

Also, we use jest snapshots for smokescreen testing. So it would throw an error if the component didn't render, regardless of whether a ComponentDidCatch lifecycle was triggered.

I'm not suggesting that you render a different component based on environment. You app code should always render the ErrorBoundary, but the error boundary component module should be stubbed in the test environment so the error boundary is replaced with a dummy shell component that does nothing but render its children prop (basically the same as any error boundary but without the error catching behavior). As far as I know, this should not fail the snapshot tests as long as the display name of the stub component is the same as the real one. This is the approach I was suggesting first:

jest.mock('src/app/components/ErrorBoundary', () => function ErrorBoundary({children}) { return children })

To those that come across this ticket in the future, the following worked for me:

jest.mock('bugsnag-js', () => (
  () => ({
    use(plugin) {
      const boundary = plugin.init();
      delete boundary.prototype.componentDidCatch;
      return boundary;
    },
  })
));

It is a slightly modified version of the mock provided above, but it returns a function instead of an object

@dbchristopher

Just a notion, that bugsnag can be used in middleware like Saga to notify of uncaught Saga errors.
In that case if you use store in your tests, you will get the same error

Closing this off.

The new version of Bugsnag is "universal", so it can now load in Node (however I would still use caution importing Bugsnag in a test, unless you want to test that error reports are sent, or if you are setting notifyReleaseStages and releaseStage)

Was this page helpful?
0 / 5 - 0 ratings

Related issues

v3solutions picture v3solutions  ยท  6Comments

antoinerousseau picture antoinerousseau  ยท  4Comments

livthomas picture livthomas  ยท  4Comments

bathos picture bathos  ยท  6Comments

kilianc picture kilianc  ยท  5Comments