Beyond React Testing Library: Testing React Libraries (and library-like code)

Rate this content
Bookmark

When it comes to testing library code, the (usually amazing!) "Testing Library" approach quickly hits its limitations: We often need to test hot code paths to ensure additional guarantees, such as a specific order of DOM changes or a particular number of renders.
As soon as we start adding Suspense to the picture, it even gets almost philosophical:
How do we count a render that immediately suspended, and how do we distinguish it from a "committed" render?
How do we know which parts of the Component tree rerendered?
In the Apollo Client code base, we're using the React Profiler to create a stream of render events, enabling us to switch to a new stream-based testing method.
After testing this approach internally for a year, we have released it in a library that we want to present to the world.
I'll also briefly look into other "testing-related" problems far outside the norm that we've come across and share our solutions:
How to test libraries that bundle different code for React Server Components, streaming SSR runs, and the Browser: Testing your growingly complex `exports` fields and ensuring all those environments export the package shape you expect.
We'll even briefly look into rendering React components in different environments to test them in isolation - be it in Server Components, Streaming SSR, or simulating stream hydration in the Browser.

This talk has been presented at React Day Berlin 2024, check out the latest edition of this React Conference.

FAQ

The new testing library has been used for over a year, resulting in 544 tests in Apollo Client that have resolved many flaky tests.

You can find Lenz on GitHub as FryNias, on Twitter as Fry, and on BlueSky as Fry.dev. He is also available for Q&A on Discord.

The speaker is Lenz Wiebertronik, and the talk is titled 'Beyond Testing Library, Testing React Libraries or Library-like Code.'

Lenz addresses the challenge of testing React libraries and library-like code, particularly when the React Testing Library may not be suitable for optimizing hot code paths.

Lenz suggests using a new testing library called Testing Library React Render Stream, which leverages the React Profiler component for more reliable testing of hot code paths.

Lenz Wiebertronik is a Senior Dev Software Engineer at Apollo GraphQL, maintaining the Apollo client for web. He also maintains Redax Toolkit and is the author of RTK Query.

The key concerns are minimizing unnecessary re-renders, preventing data tearing, and ensuring extremely granular rendering.

This library allows for step-by-step assertions on renders, reduces the complexity of writing tests, and addresses the limitations of React Testing Library for certain use cases.

Lenz warns that testing approaches may change between React versions, requiring updates to tests. There's also a caution about using 'Act' as it batches renders, which may not be desired when counting renders.

Lenz Weber-Tronic
Lenz Weber-Tronic
22 min
16 Dec, 2024

Comments

Sign in or register to post your comment.
Video Summary and Transcription
My talk is called Beyond Testing Library, Testing React Libraries or Library-like Code. We want to optimize code by minimizing re-renders, avoiding tearing, and ensuring granular rendering. React Testing Library may not always be the right tool for libraries or library-like code. We test for synchronous results, but there are cases where unwanted re-renders and inconsistencies can occur. We need to avoid flaky tests and bug propagation. The new Testing Library React Render Stream library simplifies testing by replacing complex wrappers and assertions. We test for multiple independent components and ensure correct re-rendering. We introduce Suspense and DOM snapshotting to test granular rendering. The final test provides increased confidence and meets all special requirements.

1. Introduction to Beyond Testing Library

Short description:

My talk is called Beyond Testing Library, Testing React Libraries or Library-like Code. I want to optimize code by minimizing re-renders, avoiding tearing, and ensuring granular rendering. React Testing Library may not always be the right tool for libraries or library-like code. For such code, we need to consider special requirements. Let's take a look at testing useQuery hook in Apollo Client, articleQuery, or reactQuery using React Testing Library.

Hi, there. My name is Lenz. My talk is called Beyond Testing Library, Testing React Libraries or Library-like Code. A short word about me. My name is Lenz Wiebertronik. I work as a Senior Dev Software Engineer at Apollo GraphQL. And there I maintain the Apollo client for web. But in my free time, I also maintain Redax Toolkit. I'm the author of RTK Query. And due to my ADHD, I actually maintain a bunch of smaller libraries, too. You can find me on GitHub as FryNias, on Twitter as Fry, and not on the slide, but as Fry.dev on BlueSky.

Generally, why are we here? It's a bit of a hard thing because I love testing library. I think it's amazing. But it's not always the right tool for me as a library author because React Testing Library tests for eventual consistency. And in testing library, I might not always want to look for that because libraries are a hot code path and we need to optimize for that. So for the libraries that I write and maybe also libraries that you write, either open source or as an in-house library that's shared by multiple teams, or just library-like code, we might have some special requirements that are not something that you would usually test with React Testing Library. So what are these? Well, first, I want to ensure that my code doesn't cause any more re-renders than absolutely necessary because this is like code that's in the middle of running everything. You want to have that optimized the way before you start optimizing your own code. So we do our best here.

Beyond the re-render thing, another important thing is tearing. That means that we don't want to mix data from the present with data that might be on the screen in the future or in the past. So inside of a hook, that could mean we return inconsistent state. Inside of a component would mean that two hooks might return inconsistent state with each other. And in your whole application, it could mean that one component here shows state from the future while another component down here shows state from the past. And then there's the third thing that I want to optimize for, which is extremely granular rendering. I only want the component that's absolutely necessary to re-render to re-render, and not its parents or its grandparents. So that said, if I had a hook like useQuery in Apollo Client, articleQuery, or reactQuery, how would I test that? Let's look at an example with the React Testing Library first, and this is a very common test, I believe. Here, we would start rendering our useQuery hook, and then we would start making assertions on that. So first we would test for this case on the left, loading should be true and data should be undefined. And then we test for this case on the right, where loading is false, but data is hello world.

2. Testing for Synchronous Results

Short description:

We test for synchronous things, but there are cases where other factors might cause green results that we want to avoid. For example, setState calls or usingExternalStore can cause unwanted re-renders. There are also tearing cases where loading is true but data already has the final result. These inconsistencies can lead to incorrect values being displayed.

The way we do that here is that we test for the things that can be tested synchronously, and then we wait until loading is false, and then we test for data to be equal hello world. But of course, this is the happy path, this test will always be green, but other things might also be green and we might want to avoid them. So let's look at this case, and that's a very common case where we might have like another setState call inside of our hook that doesn't really relate to the output of the hook, but it causes a re-render. Another example would be a usingExternalStore call that does the same. We want to avoid that, but with this type of test, we really have no way of determining if it was the case. Something else would be a tearing case where loading would still be true, but data would also already reach the final result. So here we have an inconsistent return value, and the way we test that, it just stays green because we just test for loading to be false, and in the meantime, data could take any value. That could even get further, and loading could also take a different value. So in this case, just a bunch of puppies. And data could have a completely unrelated value, and we would not be able to detect that with this test or most other tests too.

Check out more articles and videos

We constantly think of articles and videos that might spark Git people interest / skill us up or help building a stellar career

Integration Testing for React Native Apps
React Finland 2021React Finland 2021
25 min
Integration Testing for React Native Apps
My unpopular opinion is that testing is ... important. How do you test your React Native apps? In this presentation I will show how to run full integrations tests using Cypress while the RN app is running in the browser. This method can cover most of the application's code and be effective at finding logical errors and mistakes when calling the server APIs.
Cypress Component Testing vs React Testing Library
TestJS Summit 2023TestJS Summit 2023
25 min
Cypress Component Testing vs React Testing Library
Watch video: Cypress Component Testing vs React Testing Library
The Talk discusses the differences between Cypress component testing and React Testing Library (RTL). It highlights the benefits of using Cypress Component Testing, such as easier handling of complex components and a more stable testing experience in CI. The comparison between SignOn and Jest focuses on low-level spying and mocking capabilities. The comparison between Cypress Intercept and Mock Service Worker (MSW) examines their network spy and mocking capabilities. The Talk also emphasizes the superior developer experience and observability provided by Cypress component testing compared to RTL.

Workshops on related topic

Designing Effective Tests With React Testing Library
React Summit 2023React Summit 2023
151 min
Designing Effective Tests With React Testing Library
Top Content
Featured Workshop
Josh Justice
Josh Justice
React Testing Library is a great framework for React component tests because there are a lot of questions it answers for you, so you don’t need to worry about those questions. But that doesn’t mean testing is easy. There are still a lot of questions you have to figure out for yourself: How many component tests should you write vs end-to-end tests or lower-level unit tests? How can you test a certain line of code that is tricky to test? And what in the world are you supposed to do about that persistent act() warning?
In this three-hour workshop we’ll introduce React Testing Library along with a mental model for how to think about designing your component tests. This mental model will help you see how to test each bit of logic, whether or not to mock dependencies, and will help improve the design of your components. You’ll walk away with the tools, techniques, and principles you need to implement low-cost, high-value component tests.
Table of contents- The different kinds of React application tests, and where component tests fit in- A mental model for thinking about the inputs and outputs of the components you test- Options for selecting DOM elements to verify and interact with them- The value of mocks and why they shouldn’t be avoided- The challenges with asynchrony in RTL tests and how to handle them
Prerequisites- Familiarity with building applications with React- Basic experience writing automated tests with Jest or another unit testing framework- You do not need any experience with React Testing Library- Machine setup: Node LTS, Yarn