DEV Community

Cover image for Testing HTML events on React
Carlos Queiroz
Carlos Queiroz

Posted on • Updated on

Testing HTML events on React

What's up, Techs!

I would like to share with you a method on how to create tests for HTML events. I'll do this sharing a progressive image component that I built.

Let's take a look:

const ImageLoader = ({ src, fallbackImage = '' }) => {
  const [isLoading, setIsLoading] = useState(true);
  const imageRef = useRef(null);

  const onLoadFinished = () => {
    setIsLoading(false);
  };

  const onLoadError = () => {
    const imageObject = imageRef.current;
    imageObject.src = fallbackImage;
    setIsLoading(false);
    imageObject.onerror = null;
  };

  useEffect(() => {
    const imageObject = imageRef.current;
    imageObject.src = src;
  }, [src]);

  return (
    <Container isLoading={isLoading}>
      <Image
        ref={imageRef}
        onError={onLoadError}
        onLoad={onLoadFinished}
      />
      {isLoading && <div>Carregando...</div>}
    </Container>
  );
};

export default ImageLoader;

For this component, I received two important properties: src and fallbackImage.
It is a simple component where we are monitoring the onload and onerror events.

Through these events, we can catch if our image was loaded or failed. In case of failure, the src property will receive the fallbackImage path.

const onLoadError = () => {
    const imageObject = imageRef.current;
    imageObject.src = fallbackImage;
    setIsLoading(false);
    imageObject.onerror = null;
  };

Building the test

For the tests, we will use the React Testing Library (RTL) that is the default testing library for React.
I like this library because we test the user behavior and not implementation details. We can think that we are testing what the user is seeing and not what is inside the code.

Our test will guarantee that our component is receiving the correct image path even with success or failure.

I need to confess that when I was trying to build this test I had no idea how to simulate events. I was thinking of taking the image as a reference and trying to dispatch an event with dispatchEvent, but it was not like a semantic way.

So, what I did was going to Google and search for a solution. What I found was, for me, something interesting: React has your own test utils library. I'm talking about ReactTestUtils.

At first sight, it was solving my problem. Actually this library resolves my problem. Let's see:

it('should be able to show skeleton during the loading and when finished loading image needs to have src property equals send by parameter.', async () => {
    render(
      <ImageLoader
        src="image-teste.png"
        fallbackImage="fallback-image.png"
        alt="image-test"
      />,
    );

const imageRef = screen.getByAltText('image-test');

ReactTestUtils.Simulate.load(imageRef);

expect(imageRef).toHaveAttribute('src', 'image-teste.png');
  });

This solution looked like for me semantically well write and works fine. The PR was approved, by the way.

After that, I went back to the code, and I was thinking about how to write about this solution for others who are having the same problem as me and talking to a friend of mine, I was remembered that RTL has a function that also firing HTML events: fireEvent.

I used to use this function to fire click events, for example, but I changed to the userEvent function as the RTL recommends and due to this, I completely forgot of the fireEvent function.

Thus, I rewrite the test with the fireEvent util function. Let's have a look:

...
    render(
      <ImageLoader
        src="image-teste.png"
        fallbackImage="fallback-image.png"
        alt="image-test"
      />,
    );

const imageRef = screen.getByAltText('image-test');

fireEvent.load(imageRef);

expect(imageRef).toHaveAttribute('src', 'image-teste.png');
  });

Here is the test for the error case:

...
    render(
      <ImageLoader
        src="image-teste.png"
        fallbackImage="fallback-image.png"
        alt="image-test"
      />,
    );

    const imageRef = screen.getByAltText('image-test');

    fireEvent.error(imageRef);

expect(imageRef).toHaveAttribute('src', 'fallback-image.png');
  });

I think that the code did not change so much, but now it looks even more clear and simple.

So, if you use the RTL library and need to simulate an HMTL event, you probably should use fireEvent.

I hope that this little article was helpful to you.

Leave a comment with a suggestion, a question, or your opinion about this.
If you want, add me to your LinkedIn network, and let's chat. I love to learn and talk about JavaScript.

Bye!

Discussion (0)