Skip to content
This repository was archived by the owner on Jul 30, 2020. It is now read-only.

find* methods does not work as expected? #82

Closed
causztic opened this issue Nov 18, 2019 · 9 comments
Closed

find* methods does not work as expected? #82

causztic opened this issue Nov 18, 2019 · 9 comments

Comments

@causztic
Copy link

  • react-native
  • native-testing-library version: 5.0.1
  • jest-preset: @testing-library/react-native
  • react-native version: 16.12.0
  • node version: 11

Relevant code or config:

    const button = await findByTestId('button-id');
    fireEvent.press(button);
    const button = getByTestId('button-id');
    fireEvent.press(button);

What you did:

Hi, I am trying to use the find* methods to attempt to wait for an element to appear. The second block works but the first one doesn't. Shouldn't both be equivalent?

What happened:

I got the error here: : Timeout - Async callback was not invoked within the 5000ms timeout specified by jest.setTimeout.Timeout - Async callback was not invoked within the 5000ms timeout specified by jest.setTimeout.Error:

Reproduction:

In fact, I wasn't able to run the async method in the README - I get this error:
Screenshot 2019-11-18 at 6 16 28 PM

Problem description:

Both methods should work similarly because the Promise in the find implementation should resolve once the get* returns true

Can you help us fix this issue by submitting a pull request?

I can try if I have a direction how to fix this!

@bcarroll22
Copy link
Collaborator

Hi, thanks for the issue!

Without some more code to work off of it'll be a little hard to debug unfortunately, but we'll give it a shot. My guess is findBy* isn't working because the element isn't appearing asynchronously, it's already there. If the element is already in the tree when findBy* runs, it will fail because the element is already there. getBy* works because it's there, so it can find it right away. If it isn't there on first run, getBy* will fail and throw an error.

Then when it comes to the example from the site, it probably needs updated 😬I should look into that.

@causztic
Copy link
Author

After poking around the code it seems that findBy* is a wrapper with setTimeout for getBy*, but that would technically resolve after the interval of 50 ms right?

For the code example I just used the code on the README.md and it fails on the async method.

@causztic
Copy link
Author

causztic commented Nov 19, 2019

I tried a couple of permutations here:

import React, { useState } from 'react';
import { Button, Text, View } from 'react-native';
import {
  fireEvent, render, wait, waitForElement
} from '@testing-library/react-native';

function Example() {
  const [show, setShow] = useState(false);

  return (
    <View>
      <Button
        testID="button"
        onPress={() => setTimeout(setShow(!show), 500)}
      >
        Press this to change state
      </Button>
      { show && <Text testID="test">asdf</Text> }
    </View>
  );
}

test('test by queryByTestId', async () => {
  const { queryByTestId, getByTestId } = render(
    <Example />,
  );
  fireEvent.press(getByTestId('button'));
  await wait(() => expect(queryByTestId('test')).toBeTruthy()); // works
});

test('test by waitForElement', async () => {
  const { queryByTestId } = render(
    <Example />,
  );
  const element = await waitForElement(() => queryByTestId('test'));
  expect(element).toBeTruthy(); // does not work
});

test('test by findByTestId', async () => {
  const { findByTestId } = render(
    <Example />,
  );
  const element = await findByTestId('test');
  expect(element).toBeTruthy(); // does not work
});

Maybe this could help?

@bcarroll22
Copy link
Collaborator

The problem is that the async helpers expect that on their first try they’re going to throw because the element isn’t there. If the element is already in the DOM, just use getBy or queryBy. Only use findBy if you expect the element to not be there and appear asynchronously

@causztic
Copy link
Author

causztic commented Nov 19, 2019

I see, but on the example I have provided, the item with the testID of test only appears after the timeout of 500ms, so why does the 2nd and 3rd test not work?

@bcarroll22
Copy link
Collaborator

There are multiple issues with your examples.

Your first test passes because you aren’t making any assertions, so there’s nothing to fail. Also a queryBy never throws an error, it just returns null, so nothing would be broken in any case, it just does nothing.

Your second and third fail because you never press the button that makes the element with the test id you’re looking for show.

You should use getBy, not queryBy, when making async assertions like waitForElement because as I said, the async helpers rely on errors being thrown and queryBy won’t throw errors, it just returns null or an empty array.

Lastly, the whole second and third tests are probably acting a little buggy because the onPress function is probably not right. It should be onPress={() => setTimeout(() => setShow(!show), 500)}

I’m going to go ahead and close this because I’m confident there’s no bug in the library itself, these helpers work for a lot of folks every day and we have a lot of tests that double check this behavior. I’m sorry for the confusion on the example, I’d encourage you to open an issue or even a PR over there if you’d like to fix the examples.

If you’re looking for more help getting started, I’d check out the testing library spectrum chat or stack overflow, the difference in query types and the way queries work are generic testing library questions, they’re not specific to react native. The way they work for us are the same way they work in all of the other implementations.

Good luck!

@causztic
Copy link
Author

causztic commented Nov 19, 2019

Hi, I would like to point out that for the first test, if I were to replace the wait with the following:
await wait(() => expect(queryByTestId('testasdf')).toBeTruthy());
it fails, so your point about no assertion is incorrect.

I have also updated the setTimeout to your recommendation and it does not work either.

You are right about the incorrect tests for the 2nd and 3rd, and I have updated them to the following:

test('test by waitForElement', async () => {
  const { queryByTestId, getByTestId } = render(
    <Example />,
  );
  fireEvent.press(getByTestId('button'));
  const element = await waitForElement(() => queryByTestId('test'));
  expect(element).toBeTruthy(); // does not work
});

test('test by findByTestId', async () => {
  const { findByTestId, getByTestId } = render(
    <Example />,
  );
  fireEvent.press(getByTestId('button'));
  const element = await findByTestId('test');
  expect(element).toBeTruthy(); // does not work
});

and both still does not work. Could you have another look at this?

@causztic
Copy link
Author

causztic commented Nov 19, 2019

Hi, I managed to solve this issue by disabling jest.useFakeTimers();.

I will do a PR to update the README so that other people will not fall into the same problem. BTW you are incorrect on find*: async helpers do work even if the item was already generated, see:

import React, {useState} from 'react';
import {Button} from 'react-native';
import {render, fireEvent} from '@testing-library/react-native';

const TestComponent = () => {
  const [show, setShow] = useState(false);

  return (
    <div>
      <Button
        testID="button"
        onPress={() => setTimeout(() => setShow(!show), 500)}
      >
        Press Me
      </Button>
      {show && <p testID="test">asdf</p>}
    </div>
  );
};

test('test by queryByTestId', async () => {
  const {findByTestId, getByTestId} = render(<TestComponent />);
  fireEvent.press(await findByTestId('button'));
  const element = await findByTestId('test');
  expect(element).toBeTruthy();
});

Thanks for your help!

@LRNZ09
Copy link

LRNZ09 commented Mar 16, 2020

#83 (comment)

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