From 16ceb3973bc7c7ca15cd5e8eca8641a0df84f0e9 Mon Sep 17 00:00:00 2001 From: Maciej Jastrzebski Date: Mon, 2 May 2022 13:29:45 +0200 Subject: [PATCH 1/5] docs: initial act doc --- README.md | 4 + website/docs/API.md | 10 ++ website/docs/UnderstandingAct.md | 222 +++++++++++++++++++++++++++++++ 3 files changed, 236 insertions(+) create mode 100644 website/docs/UnderstandingAct.md diff --git a/README.md b/README.md index d929418e8..cc409bfa6 100644 --- a/README.md +++ b/README.md @@ -143,6 +143,10 @@ The [public API](https://callstack.github.io/react-native-testing-library/docs/a - [Migration to 7.0](https://callstack.github.io/react-native-testing-library/docs/migration-v7) - [Migration to 2.0](https://callstack.github.io/react-native-testing-library/docs/migration-v2) +## Troubleshooting + +- [Understanding `act` function](https://callstack.github.io/react-native-testing-library/docs/undestanding-act) + ## Related External Resources - [Real world extensive examples repo](https://github.com/vanGalilea/react-native-testing) diff --git a/website/docs/API.md b/website/docs/API.md index 097a109f7..563d9bc74 100644 --- a/website/docs/API.md +++ b/website/docs/API.md @@ -372,6 +372,10 @@ test('waiting for an Banana to be ready', async () => { In order to properly use `waitFor` you need at least React >=16.9.0 (featuring async `act`) or React Native >=0.61 (which comes with React >=16.9.0). ::: +:::note +If you receive warnings related to `act()` function consult our [Undestanding Act](./UnderstandingAct.md) function document. +::: + ## `waitForElementToBeRemoved` - [`Example code`](https://github.com/callstack/react-native-testing-library/blob/main/src/__tests__/waitForElementToBeRemoved.test.tsx) @@ -408,6 +412,10 @@ You can use any of `getBy`, `getAllBy`, `queryBy` and `queryAllBy` queries for ` In order to properly use `waitForElementToBeRemoved` you need at least React >=16.9.0 (featuring async `act`) or React Native >=0.61 (which comes with React >=16.9.0). ::: +:::note +If you receive warnings related to `act()` function consult our [Undestanding Act](./UnderstandingAct.md) function document. +::: + ## `within`, `getQueriesForElement` - [`Example code`](https://github.com/callstack/react-native-testing-library/blob/main/src/__tests__/within.test.tsx) @@ -466,6 +474,8 @@ expect(submitButtons).toHaveLength(3); // expect 3 elements Useful function to help testing components that use hooks API. By default any `render`, `update`, `fireEvent`, and `waitFor` calls are wrapped by this function, so there is no need to wrap it manually. This method is re-exported from [`react-test-renderer`](https://github.com/facebook/react/blob/main/packages/react-test-renderer/src/ReactTestRenderer.js#L567]). +Consult our [Undestanding Act](./UnderstandingAct.md) function document for more understanding of its intricacies. + ## `renderHook` Defined as: diff --git a/website/docs/UnderstandingAct.md b/website/docs/UnderstandingAct.md new file mode 100644 index 000000000..1cf7b194e --- /dev/null +++ b/website/docs/UnderstandingAct.md @@ -0,0 +1,222 @@ +--- +id: understanding-act +title: Understanding Act function +--- + +# Understanding Act function + +When writing RNTL tests one of the things that confuses developers the most are cryptic `act()` function errors logged into console. In this article I will try to build an understanding of the purpose and behaviour of `act()` so you can build your tests with more confidence. + +# The act warnings + +Let’s start with typical `act()` warnings logged to console. There are two kinds of these issues, let’s call the first one sync `act()` warnings: + +``` +Warning: An update to Component inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ +``` + +The second one relates to async usage of `act` so let’s call it async `act` error: + +``` +Warning: You called act(async () => ...) without await. This could lead to unexpected testing behaviour, interleaving multiple act calls and mixing their scopes. You should - await act(async () => ...); +``` + +# Sync act + +## Responsibility + +This function is intended only for using in automated tests and works only in development mode. Attempting to use it in production build will throw an error. + +The responsibility for `act` function is to make React renders and updates work in tests in a similar way they work in real application by grouping and executing related units of interaction (e.g. renders, effects, etc) together. + +To showcase that behaviour let make a small experiment. First we define a function component that uses `useEffect` hook in a trivial way. + +```jsx +function TestComponent() { + const [count, setCount] = React.useState(0); + React.useEffect(() => { + setCount((c) => c + 1); + }, []); + + return Count {count}; +} +``` + +In the following tests we will directly use `ReactTestRenderer` instead of RNTL `render` function to render our component for tests. In order to expose familiar queries like `getByText` we will use `within` function from RNTL. + +```jsx +test('render without act', () => { + const renderer = TestRenderer.create(); + + // Bind RNTL queries for root element. + const view = within(renderer.root); + expect(view.getByText('Count 0')).toBeTruthy(); +}); +``` + +When testing without `act` call wrapping rendering call, we see that the assertion runs just after the rendering but before `useEffect`hooks effects are applied. Which is not what we expected in our tests. + +```jsx +test('render with act', () => { + let renderer: ReactTestRenderer; + act(() => { + renderer = TestRenderer.create(); + }); + + // Bind RNTL queries for root element. + const view = within(renderer!.root); + expect(view.getByText('Count 1')).toBeTruthy(); +}); +``` + +When wrapping rendering call with `act` we see that the changes caused by `useEffect` hook have been applied as we would expect. + +## When to use act + +The name `act` comes from [Arrange-Act-Assert](http://wiki.c2.com/?ArrangeActAssert) unit testing pattern. Which means it’s related to part of the test when we execute some actions on the component tree. + +So far we learned that `act` function allows tests to wait for all pending React interactions to be applied before we make our assertions. When using `act` we get guarantee that any state updates will be executed as well as any enqueued effects will be executed. + +Therefore, we should use `act` whenever the is some action that causes element tree to render, particularly: + +- initial render call - `ReactTestRenderer.create` call +- re-rendering of component -`renderer.update` call +- triggering any event handlers that cause component tree render + +Thankfully, for these basic cases RNTL has got you covered as our `render`, `update` and `fireEvent` calls already wrap their calls in sync `act` so that you do not have to do it explicitly. + +Note that `act`calls can be safely nested and internally form a stack of calls. However, overlapping `act` calls, which can be achieved using async version of `act`, [are not supported](https://github.com/facebook/react/blob/main/packages/react/src/ReactAct.js#L161). + +## Implementation + +As of React version of 18.1.0, the `act` implementation is defined in the [ReactAct.js source file](https://github.com/facebook/react/blob/main/packages/react/src/ReactAct.js) inside React repository. This implementation has been fairly stable since React 17.0. + +RNTL exports `act` for convenience of the users as defined in the [act.ts source file](https://github.com/callstack/react-native-testing-library/blob/main/src/act.ts). That file refers to [ReactTestRenderer.js source](https://github.com/facebook/react/blob/ce13860281f833de8a3296b7a3dad9caced102e9/packages/react-test-renderer/src/ReactTestRenderer.js#L52) file from React Test Renderer package, which finally leads to React `act` implementation mentioned above. + +# Async act + +So far we have seen synchronous version of `act` which runs its callback immediately. This can deal with things like synchronous effects or mocks using already resolved promises. However, not all component code is synchronous. Frequently our components or mocks contain some asynchronous behaviours like `setTimeout` calls or network calls. Starting from React 16.9, `act` can also be called in asynchronous mode. In such case `act` implementation checks that the passed callback returns [object resembling promise](https://github.com/facebook/react/blob/ce13860281f833de8a3296b7a3dad9caced102e9/packages/react/src/ReactAct.js#L60). + +## Basics + +Asynchronous version of `act` also is executed immediately, but the callback is not yet completed because of some asynchronous operations inside. + +Lets look at a simple example with component using `setTimeout`call to simulate asynchronous behaviour: + +```jsx +function TestAsyncComponent() { + const [count, setCount] = React.useState(0); + React.useEffect(() => { + setTimeout(() => { + setCount((c) => c + 1); + }, 50); + }, []); + + return Count {count}; +} +``` + +```jsx +test('render async natively', () => { + const view = render(); + expect(view.getByText('Count 0')).toBeTruthy(); +}); +``` + +If we test our component in a native way without handling its asynchronous behaviour we will end up with sync act warning: + +``` +Warning: An update to TestAsyncComponent inside a test was not wrapped in act(...). + +When testing, code that causes React state updates should be wrapped into act(...): + +act(() => { + /* fire events that update state */ +}); +/* assert on the output */ +``` + +Note that this is not yet the infamous async act warning. It only asks us to wrap our event code with `act` calls. However, this time our immediate state change does not originate from externally triggered events but rather forms an internal part of the component. So how can we apply `act` in such scenario? + +### Async act and fake timers + +First solution is to use jest fake timers inside out tests. + +```jsx +test('render with fake timers', () => { + jest.useFakeTimers(); + const view = render(); + + act(() => { + jest.runAllTimers(); + }); + expect(view.getByText('Count 1')).toBeTruthy(); +}); +``` + +That way we can wrap `jest.runAllTimers()` call which triggers the `setTimeout` updates inside an `act` call, hence resolving the act warning. Note that this whole code is synchronous thanks to usage of Jest fake timers. + +### Async act and real timers + +If we wanted to stick with real timers then things get a bit more complex. + +Let’s start by applying a crude solution of opening async `act()` call for the expected duration of components updates: + +```jsx +test('render with real timers - sleep', async () => { + const view = render(); + await act(async () => { + await sleep(50 + 10); // Wait a bit longer then setTimeout in component + }); + + expect(view.getByText('Count 1')).toBeTruthy(); +}); +``` + +This works correctly as we use an explicit async `act()` call that resolves the console error. However, it relies on our knowledge of exact implementation details which is a bad practice. + +Let’s try more elegant solution using `waitFor` that will wait for our desired state: + +```jsx +test('render with real timers - waitFor', async () => { + const view = render(); + + await waitFor(() => view.getByText('Count 1')); + expect(view.getByText('Count 1')).toBeTruthy(); +}); +``` + +This also works correctly, because `waitFor` calls executes async `act()` call internally. + +The above code can be simplified using `findBy` query: + +```jsx +test('render with real timers - findBy', async () => { + const view = render(); + + expect(await view.findByText('Count 1')).toBeTruthy(); +}); +``` + +This also works since `findByText` internally calls `waitFor` which uses async `act()`. + +Note that all of the above examples are async tests using & awaiting async `act()` function call. + +### Async act warnings + +If we modify any of the above async tests and remove `await` keyword, then we will trigger the notorious async `act()`warning: + +```jsx +Warning: You called act(async () => ...) without await. This could lead to unexpected testing behaviour, interleaving multiple act calls and mixing their scopes. You should - await act(async () => ...); +``` + +React decides to show this error whenever it detects that async `act()`call [has not been awaited](https://github.com/facebook/react/blob/ce13860281f833de8a3296b7a3dad9caced102e9/packages/react/src/ReactAct.js#L93). + +The exact reasons why you might see async `act()`warnings vary, but finally it means that `act()` has been called with callback that returns `Promise`-like object, but it has not been waited on. From fb51c79e3b469f6afcf63d90e3914ee1c63ca962 Mon Sep 17 00:00:00 2001 From: Maciej Jastrzebski Date: Mon, 2 May 2022 13:47:17 +0200 Subject: [PATCH 2/5] self code review --- website/docs/API.md | 2 +- website/docs/UnderstandingAct.md | 36 ++++++++++++++++---------------- website/sidebars.js | 1 + 3 files changed, 20 insertions(+), 19 deletions(-) diff --git a/website/docs/API.md b/website/docs/API.md index 563d9bc74..ab83023b2 100644 --- a/website/docs/API.md +++ b/website/docs/API.md @@ -474,7 +474,7 @@ expect(submitButtons).toHaveLength(3); // expect 3 elements Useful function to help testing components that use hooks API. By default any `render`, `update`, `fireEvent`, and `waitFor` calls are wrapped by this function, so there is no need to wrap it manually. This method is re-exported from [`react-test-renderer`](https://github.com/facebook/react/blob/main/packages/react-test-renderer/src/ReactTestRenderer.js#L567]). -Consult our [Undestanding Act](./UnderstandingAct.md) function document for more understanding of its intricacies. +Consult our [Undestanding Act function](./UnderstandingAct.md) document for more understanding of its intricacies. ## `renderHook` diff --git a/website/docs/UnderstandingAct.md b/website/docs/UnderstandingAct.md index 1cf7b194e..44fe1e2df 100644 --- a/website/docs/UnderstandingAct.md +++ b/website/docs/UnderstandingAct.md @@ -3,11 +3,9 @@ id: understanding-act title: Understanding Act function --- -# Understanding Act function - When writing RNTL tests one of the things that confuses developers the most are cryptic `act()` function errors logged into console. In this article I will try to build an understanding of the purpose and behaviour of `act()` so you can build your tests with more confidence. -# The act warnings +## The act warnings Let’s start with typical `act()` warnings logged to console. There are two kinds of these issues, let’s call the first one sync `act()` warnings: @@ -25,12 +23,14 @@ act(() => { The second one relates to async usage of `act` so let’s call it async `act` error: ``` -Warning: You called act(async () => ...) without await. This could lead to unexpected testing behaviour, interleaving multiple act calls and mixing their scopes. You should - await act(async () => ...); +Warning: You called act(async () => ...) without await. This could lead to unexpected +testing behaviour, interleaving multiple act calls and mixing their scopes. You should +- await act(async () => ...); ``` -# Sync act +## Synchronous act -## Responsibility +### Responsibility This function is intended only for using in automated tests and works only in development mode. Attempting to use it in production build will throw an error. @@ -78,7 +78,7 @@ test('render with act', () => { When wrapping rendering call with `act` we see that the changes caused by `useEffect` hook have been applied as we would expect. -## When to use act +### When to use act The name `act` comes from [Arrange-Act-Assert](http://wiki.c2.com/?ArrangeActAssert) unit testing pattern. Which means it’s related to part of the test when we execute some actions on the component tree. @@ -94,17 +94,17 @@ Thankfully, for these basic cases RNTL has got you covered as our `render`, `upd Note that `act`calls can be safely nested and internally form a stack of calls. However, overlapping `act` calls, which can be achieved using async version of `act`, [are not supported](https://github.com/facebook/react/blob/main/packages/react/src/ReactAct.js#L161). -## Implementation +### Implementation As of React version of 18.1.0, the `act` implementation is defined in the [ReactAct.js source file](https://github.com/facebook/react/blob/main/packages/react/src/ReactAct.js) inside React repository. This implementation has been fairly stable since React 17.0. RNTL exports `act` for convenience of the users as defined in the [act.ts source file](https://github.com/callstack/react-native-testing-library/blob/main/src/act.ts). That file refers to [ReactTestRenderer.js source](https://github.com/facebook/react/blob/ce13860281f833de8a3296b7a3dad9caced102e9/packages/react-test-renderer/src/ReactTestRenderer.js#L52) file from React Test Renderer package, which finally leads to React `act` implementation mentioned above. -# Async act +## Asynchronous act So far we have seen synchronous version of `act` which runs its callback immediately. This can deal with things like synchronous effects or mocks using already resolved promises. However, not all component code is synchronous. Frequently our components or mocks contain some asynchronous behaviours like `setTimeout` calls or network calls. Starting from React 16.9, `act` can also be called in asynchronous mode. In such case `act` implementation checks that the passed callback returns [object resembling promise](https://github.com/facebook/react/blob/ce13860281f833de8a3296b7a3dad9caced102e9/packages/react/src/ReactAct.js#L60). -## Basics +### Asynchronous code Asynchronous version of `act` also is executed immediately, but the callback is not yet completed because of some asynchronous operations inside. @@ -145,9 +145,9 @@ act(() => { Note that this is not yet the infamous async act warning. It only asks us to wrap our event code with `act` calls. However, this time our immediate state change does not originate from externally triggered events but rather forms an internal part of the component. So how can we apply `act` in such scenario? -### Async act and fake timers +### Solution with fake timers -First solution is to use jest fake timers inside out tests. +First solution is to use jest fake timers inside out tests: ```jsx test('render with fake timers', () => { @@ -163,11 +163,9 @@ test('render with fake timers', () => { That way we can wrap `jest.runAllTimers()` call which triggers the `setTimeout` updates inside an `act` call, hence resolving the act warning. Note that this whole code is synchronous thanks to usage of Jest fake timers. -### Async act and real timers - -If we wanted to stick with real timers then things get a bit more complex. +### Solution with real timers -Let’s start by applying a crude solution of opening async `act()` call for the expected duration of components updates: +If we wanted to stick with real timers then things get a bit more complex. Let’s start by applying a crude solution of opening async `act()` call for the expected duration of components updates: ```jsx test('render with real timers - sleep', async () => { @@ -209,12 +207,14 @@ This also works since `findByText` internally calls `waitFor` which uses async ` Note that all of the above examples are async tests using & awaiting async `act()` function call. -### Async act warnings +### Async act warning If we modify any of the above async tests and remove `await` keyword, then we will trigger the notorious async `act()`warning: ```jsx -Warning: You called act(async () => ...) without await. This could lead to unexpected testing behaviour, interleaving multiple act calls and mixing their scopes. You should - await act(async () => ...); +Warning: You called act(async () => ...) without await. This could lead to unexpected +testing behaviour, interleaving multiple act calls and mixing their scopes. You should +- await act(async () => ...); ``` React decides to show this error whenever it detects that async `act()`call [has not been awaited](https://github.com/facebook/react/blob/ce13860281f833de8a3296b7a3dad9caced102e9/packages/react/src/ReactAct.js#L93). diff --git a/website/sidebars.js b/website/sidebars.js index 289508d94..1676a6aea 100644 --- a/website/sidebars.js +++ b/website/sidebars.js @@ -8,6 +8,7 @@ module.exports = { 'migration-v2', 'how-should-i-query', 'eslint-plugin-testing-library', + 'understanding-act', ], Examples: ['react-navigation', 'redux-integration'], }, From f822f3b4e68ed815d179adc2ba3e7142f7c3a3ec Mon Sep 17 00:00:00 2001 From: Maciej Jastrzebski Date: Mon, 2 May 2022 13:56:09 +0200 Subject: [PATCH 3/5] docs: self code review --- website/docs/UnderstandingAct.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/website/docs/UnderstandingAct.md b/website/docs/UnderstandingAct.md index 44fe1e2df..1c611797a 100644 --- a/website/docs/UnderstandingAct.md +++ b/website/docs/UnderstandingAct.md @@ -220,3 +220,7 @@ testing behaviour, interleaving multiple act calls and mixing their scopes. You React decides to show this error whenever it detects that async `act()`call [has not been awaited](https://github.com/facebook/react/blob/ce13860281f833de8a3296b7a3dad9caced102e9/packages/react/src/ReactAct.js#L93). The exact reasons why you might see async `act()`warnings vary, but finally it means that `act()` has been called with callback that returns `Promise`-like object, but it has not been waited on. + +## References + +- [React `act` implementation source](https://github.com/facebook/react/blob/main/packages/react/src/ReactAct.js) From 89fc81892a9160d9e40364d7b172f3b070d53974 Mon Sep 17 00:00:00 2001 From: Maciej Jastrzebski Date: Thu, 2 Jun 2022 11:43:42 +0200 Subject: [PATCH 4/5] docs: applied type fixes --- website/docs/UnderstandingAct.md | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/website/docs/UnderstandingAct.md b/website/docs/UnderstandingAct.md index 1c611797a..e4f7ef90f 100644 --- a/website/docs/UnderstandingAct.md +++ b/website/docs/UnderstandingAct.md @@ -3,11 +3,11 @@ id: understanding-act title: Understanding Act function --- -When writing RNTL tests one of the things that confuses developers the most are cryptic `act()` function errors logged into console. In this article I will try to build an understanding of the purpose and behaviour of `act()` so you can build your tests with more confidence. +When writing RNTL tests one of the things that confuses developers the most are cryptic [`act()`](https://reactjs.org/docs/testing-recipes.html#act) function errors logged into console. In this article I will try to build an understanding of the purpose and behaviour of `act()` so you can build your tests with more confidence. ## The act warnings -Let’s start with typical `act()` warnings logged to console. There are two kinds of these issues, let’s call the first one sync `act()` warnings: +Let’s start with typical `act()` warnings logged to console. There are two kinds of these issues, let’s call the first one a sync `act()` warning: ``` Warning: An update to Component inside a test was not wrapped in act(...). @@ -20,7 +20,7 @@ act(() => { /* assert on the output */ ``` -The second one relates to async usage of `act` so let’s call it async `act` error: +The second one relates to async usage of `act` so let’s call it an "async `act`" error: ``` Warning: You called act(async () => ...) without await. This could lead to unexpected @@ -84,21 +84,21 @@ The name `act` comes from [Arrange-Act-Assert](http://wiki.c2.com/?ArrangeActAss So far we learned that `act` function allows tests to wait for all pending React interactions to be applied before we make our assertions. When using `act` we get guarantee that any state updates will be executed as well as any enqueued effects will be executed. -Therefore, we should use `act` whenever the is some action that causes element tree to render, particularly: +Therefore, we should use `act` whenever there is some action that causes element tree to render, particularly: - initial render call - `ReactTestRenderer.create` call - re-rendering of component -`renderer.update` call - triggering any event handlers that cause component tree render -Thankfully, for these basic cases RNTL has got you covered as our `render`, `update` and `fireEvent` calls already wrap their calls in sync `act` so that you do not have to do it explicitly. +Thankfully, for these basic cases RNTL has got you covered as our `render`, `update` and `fireEvent` methods already wrap their calls in sync `act` so that you do not have to do it explicitly. -Note that `act`calls can be safely nested and internally form a stack of calls. However, overlapping `act` calls, which can be achieved using async version of `act`, [are not supported](https://github.com/facebook/react/blob/main/packages/react/src/ReactAct.js#L161). +Note that `act` calls can be safely nested and internally form a stack of calls. However, overlapping `act` calls, which can be achieved using async version of `act`, [are not supported](https://github.com/facebook/react/blob/main/packages/react/src/ReactAct.js#L161). ### Implementation As of React version of 18.1.0, the `act` implementation is defined in the [ReactAct.js source file](https://github.com/facebook/react/blob/main/packages/react/src/ReactAct.js) inside React repository. This implementation has been fairly stable since React 17.0. -RNTL exports `act` for convenience of the users as defined in the [act.ts source file](https://github.com/callstack/react-native-testing-library/blob/main/src/act.ts). That file refers to [ReactTestRenderer.js source](https://github.com/facebook/react/blob/ce13860281f833de8a3296b7a3dad9caced102e9/packages/react-test-renderer/src/ReactTestRenderer.js#L52) file from React Test Renderer package, which finally leads to React `act` implementation mentioned above. +RNTL exports `act` for convenience of the users as defined in the [act.ts source file](https://github.com/callstack/react-native-testing-library/blob/main/src/act.ts). That file refers to [ReactTestRenderer.js source](https://github.com/facebook/react/blob/ce13860281f833de8a3296b7a3dad9caced102e9/packages/react-test-renderer/src/ReactTestRenderer.js#L52) file from React Test Renderer package, which finally leads to React act implementation in ReactAct.js (already mentioned above). ## Asynchronous act @@ -108,7 +108,7 @@ So far we have seen synchronous version of `act` which runs its callback immedia Asynchronous version of `act` also is executed immediately, but the callback is not yet completed because of some asynchronous operations inside. -Lets look at a simple example with component using `setTimeout`call to simulate asynchronous behaviour: +Lets look at a simple example with component using `setTimeout` call to simulate asynchronous behaviour: ```jsx function TestAsyncComponent() { @@ -147,7 +147,7 @@ Note that this is not yet the infamous async act warning. It only asks us to wra ### Solution with fake timers -First solution is to use jest fake timers inside out tests: +First solution is to use Jest's fake timers inside out tests: ```jsx test('render with fake timers', () => { @@ -171,7 +171,7 @@ If we wanted to stick with real timers then things get a bit more complex. Let test('render with real timers - sleep', async () => { const view = render(); await act(async () => { - await sleep(50 + 10); // Wait a bit longer then setTimeout in component + await sleep(50 + 10); // Wait a bit longer than setTimeout in `TestAsyncComponent` }); expect(view.getByText('Count 1')).toBeTruthy(); @@ -191,7 +191,7 @@ test('render with real timers - waitFor', async () => { }); ``` -This also works correctly, because `waitFor` calls executes async `act()` call internally. +This also works correctly, because `waitFor` call executes async `act()` call internally. The above code can be simplified using `findBy` query: @@ -224,3 +224,4 @@ The exact reasons why you might see async `act()`warnings vary, but finally it m ## References - [React `act` implementation source](https://github.com/facebook/react/blob/main/packages/react/src/ReactAct.js) +- [React testing recipes: `act()`](https://reactjs.org/docs/testing-recipes.html#act) From 8b14c2bc4adc8b323866435951fe5a2972203bb1 Mon Sep 17 00:00:00 2001 From: Maciej Jastrzebski Date: Thu, 2 Jun 2022 12:04:42 +0200 Subject: [PATCH 5/5] docs: further updates --- website/docs/UnderstandingAct.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/website/docs/UnderstandingAct.md b/website/docs/UnderstandingAct.md index e4f7ef90f..f0414c76a 100644 --- a/website/docs/UnderstandingAct.md +++ b/website/docs/UnderstandingAct.md @@ -7,7 +7,7 @@ When writing RNTL tests one of the things that confuses developers the most are ## The act warnings -Let’s start with typical `act()` warnings logged to console. There are two kinds of these issues, let’s call the first one a sync `act()` warning: +Let’s start with typical `act()` warnings logged to console. There are two kinds of these issues, let’s call the first one the "sync `act()`" warning: ``` Warning: An update to Component inside a test was not wrapped in act(...). @@ -20,7 +20,7 @@ act(() => { /* assert on the output */ ``` -The second one relates to async usage of `act` so let’s call it an "async `act`" error: +The second one relates to async usage of `act` so let’s call it the "async `act`" error: ``` Warning: You called act(async () => ...) without await. This could lead to unexpected @@ -171,7 +171,7 @@ If we wanted to stick with real timers then things get a bit more complex. Let test('render with real timers - sleep', async () => { const view = render(); await act(async () => { - await sleep(50 + 10); // Wait a bit longer than setTimeout in `TestAsyncComponent` + await sleep(100); // Wait a bit longer than setTimeout in `TestAsyncComponent` }); expect(view.getByText('Count 1')).toBeTruthy(); @@ -219,7 +219,7 @@ testing behaviour, interleaving multiple act calls and mixing their scopes. You React decides to show this error whenever it detects that async `act()`call [has not been awaited](https://github.com/facebook/react/blob/ce13860281f833de8a3296b7a3dad9caced102e9/packages/react/src/ReactAct.js#L93). -The exact reasons why you might see async `act()`warnings vary, but finally it means that `act()` has been called with callback that returns `Promise`-like object, but it has not been waited on. +The exact reasons why you might see async `act()` warnings vary, but finally it means that `act()` has been called with callback that returns `Promise`-like object, but it has not been waited on. ## References