0

From the documentation, findBy queries return a Promise. But it seems like using these queries with Promise.prototype.catch() doesn't work in cases where using them with async/await + try...catch does.

For example, 'not found' gets logged here as expected:

const { screen } = require('@testing-library/dom');

beforeAll(() => {
  document.body.innerHTML = `
    <header></header>
  `;
});

test('DOM', async () => {
  try {
    await screen.findByRole('aaaaa');
    console.log('found');
  } catch {
    console.log('not found');
  }
});

However, nothing gets logged here:

test('DOM', () => {
  screen.findByRole('aaaaa')
    .then(() => {
      console.log('found');
    })
    .catch(() => {
      console.log('not found');
    });
});

Is there a reason for this?

apikosir
  • 35
  • 2
  • 5

2 Answers2

1

You simply need to return the Promise so your testing framework (probably Jest) knows to wait for the test to finish. Otherwise Jest has no way of knowing that this test is asynchronous, using the async keyword implicitly returns a Promise.

const { screen } = require('@testing-library/dom');

test('DOM', () => {
  return screen.findByRole('aaaaa')
    .then(() => {
      console.log('found');
    })
    .catch(() => {
      console.log('not found');
    });
});
emeraldsanto
  • 4,330
  • 1
  • 12
  • 25
  • I think though it is a good tip, the answer is not relevant for the question about catching with Promises. – Jose Marin Jan 28 '21 at 21:34
  • Except I think it is, since OP is not returning a Promise the Jest environment gets torn down before the error has a chance to be caught and logged. Using my solution will address this. – emeraldsanto Jan 28 '21 at 21:43
  • I doubt that your example in case screen.findByRole('aaa') throw an exception it will be catch in the catch you wrote to log 'not found'. The question was to explain why it was not being catch... and I think in your code is even catch yet. – Jose Marin Jan 28 '21 at 21:55
  • Then by all means, try it out :) – emeraldsanto Jan 28 '21 at 21:56
  • I have tried it... and it didn't catch. You can edit your example as I did with a fake that throws and see if you get a clean 'not found'. – Jose Marin Jan 28 '21 at 21:59
  • thanks, didn't consider it might be because of Jest. returning the Promise chain does result in the .catch() callback being called. also noticed return isn't necessary if findByRole() succeeds in finding an element, maybe that's because if it doesn't find an element it'll wait around & reject at a later time – apikosir Jan 28 '21 at 22:33
  • @apikosir Yes, all `findBy*` queries reject after a default timeout of 1000ms, as mentioned in their [docs](https://testing-library.com/docs/queries/about#types-of-queries). It is still good practice to return the Promise even if you're certain the element will be present. Please mark my solution as answer if it has resolved your issue, thank you! – emeraldsanto Jan 28 '21 at 22:49
0

Your two examples are not equivalent. The statement screen.findByRole('aaaaa') is not inside of a Promise. Setting up the function as async will wrap the function in a Promise. Then, the equivalent to the async await should be written as you see below.

I have adapted the code to work with the case of function returning a type of error in addition to your requirement to deal with "throwing an error in a Promise..."

    /// I added fake up implementations just for ilustration
    let screen={
      findByRole:
        ()=>new Promise((resolve, reject) => setTimeout(
          ()=>{reject(new Error('The error'))}
          ,2000
        ))
    }

    // You can comment above and uncomment to test different scenarios
    //let screen={findByRole:()=>new Error('The error')}
    //let screen={findByRole:()=>5}

    function test(desc, func)
    {
      func()
    }
    /////

    test('DOM', () => {
      new Promise(
        (resolve, reject) => {
          let result = screen.findByRole('aaaaa')
          result instanceof Error? reject(result):resolve(result)
        })
        .then(() => {
          console.log('found');
        })
        .catch((e) => {
          console.log('not found and capture error: ' + e.message);
        });
    });
Jose Marin
  • 802
  • 1
  • 4
  • 15
  • unfortunately, this code doesn't produce the same output. changing resolve() to reject() does, but that would make it happen because of reject(), not findByRole() – apikosir Jan 28 '21 at 20:35
  • I thought that calling screen.findByRole('aaaaa') was producing an exception that you were not able to catch. I tested my code and it works. Is this some misunderstanding? – Jose Marin Jan 28 '21 at 21:00
  • Maybe you meant... returning an error in a Promise instead of what you wrote " throwing an error in a Promise results in a rejection". – Jose Marin Jan 28 '21 at 21:03
  • This only works because your custom implementation of `screen.findByRole` throws synchronously. The default implementation from `@testing-library/dom` rejects after 1000ms. It is also worth noting that your conditional `resolve` or `reject` will never be called since the line above it throws an error which is not handled by your code (using try/catch for example). – emeraldsanto Jan 28 '21 at 22:55
  • @apikosir throwing something asynchronous will be something impossible to catch. Even with async/await. You finally accepted the code that you said it didn't catch the error. I think marking this as the answer, it will be misleading for any other people that see it. – Jose Marin Jan 29 '21 at 00:23
  • @JoseMarin yeah the method doesn't throw asynchronously, it rejects instead. just updated the question to get rid of that part – apikosir Jan 29 '21 at 00:38
  • @apikosir then the whole question is wrong.... as the second version with "then" it will work as well. – Jose Marin Jan 29 '21 at 00:48