modernweb-dev / web

Guides, tools and libraries for modern web development.
https://modern-web.dev
MIT License
2.22k stars 289 forks source link

[test-runnner] Unable to catch error in test with @testing-library/react #1614

Open aodinok opened 3 years ago

aodinok commented 3 years ago

Reproduction repo: https://github.com/aodinok/wtr-rtl-example

Please see linked repo to reproduce the problem. This looks like some kind of conflict between testing-library and wtr. The error that is thrown inside rendered component is thrown in a way that it can't be catched by test code and appears later in the console.

I spend 2 hours debugging this and couldn't find a solution 😞 Would appreciate any help or advice!

If you throw error from other places it works and catched properly, when error is thrown from component that is rendered by testing-library - it can't be catched.

Even this simple test fails:

const TestComponent = () => { throw new Error('test'); };

describe('test, () => { it('throws', () => { expect(() => render()).to.throw(); }); });

Screenshot 2021-08-11 at 19 15 17

bennypowers commented 3 years ago

This looks like a problem in App.tsx, specifically, how you throw an error in the react function

https://blog.pusher.com/react-error-boundaries/

Error Boundaries were introduced [...] to solve the problem of being unable to handle render errors due to the declarative nature of React components.

aodinok commented 3 years ago

No, it is not because of error boundaries. Same happens if component is wrapped in ErrorBoundary, I pushed the updated example with ErrorBoundary where this issue can be reproduced as well.

In short, this doesn't work:

import * as React from 'react';
import { render, screen } from '@testing-library/react';
import { expect } from 'chai';

import App from './AppWithBoundary';

describe('<App>', () => {
  it('renders without error', () => {
    render(<App initialSimulateError={false} />)
    expect(screen.getByRole('button')).not.to.eql('null');
  });

  it('throws error', () => {

    // Neither of this works

    // try {
    //  const { getByText } = render(<App />);
    //  const createAccount = getByText(/Hello world/i);
    //   expect(document.body.contains(createAccount));
    // } catch (err) {
    //   console.log('error catch successful')
    // }

    expect(() => render(<App initialSimulateError={false} />)).to.throw();
  });
});

And even this:


  it('throws error', () => {
    try {
      expect(() => render(<App initialSimulateError={true} />)).to.throw();
    } catch (err) {
      console.log('catched error', err)
    }
    console.log('test execution end')
  });

Still failing test because of uncaught error, even though it is wrapped in try/catch. Something is definitely going wrong there.

Screenshot 2021-08-11 at 21 02 07

LarsDenBakker commented 3 years ago

This has to do with how chai and react interact witb each other, not web test runner.

If you use this assertion from chai, it expects an error to be thrown synchronously by the render function.

aodinok commented 3 years ago

Any suggestions how this can be avoided? Maybe you know other assertion library that will work for that use case?

LarsDenBakker commented 3 years ago

Unfortunately I'm not really familiar enough with react to know that

Pratik29121 commented 3 years ago

import {ErrorBoundary} from 'react-error-boundary'

test('error is thrown', async () => { const fallbackRender = jest.fn(() => null) render(

) fireEvent.click(await screen.findByText('Add todo'));

await waitFor(() => expect(fallbackRender).toHaveBeenCalled()) expect(fallbackRender.mock.calls[0].error).toMatchInlineSnapshot(/ jest will update this /) })