Skip to content
This repository was archived by the owner on Nov 23, 2024. It is now read-only.

Commit 5599c4f

Browse files
committed
Add render count helper, add basic render count assumptions to tests
1 parent 976286e commit 5599c4f

File tree

2 files changed

+85
-27
lines changed

2 files changed

+85
-27
lines changed

test/buildHooks.test.tsx

Lines changed: 68 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { createApi, fetchBaseQuery, QueryStatus } from '@rtk-incubator/rtk-query
33
import { act, fireEvent, render, screen, waitFor } from '@testing-library/react';
44
import userEvent from '@testing-library/user-event';
55
import { rest } from 'msw';
6-
import { setupApiStore, waitMs } from './helpers';
6+
import { setupApiStore, useRenderCounter, waitMs } from './helpers';
77
import { server } from './mocks/server';
88
import { AnyAction } from 'redux';
99
import { SubscriptionOptions } from '@internal/core/apiState';
@@ -54,8 +54,31 @@ afterEach(() => {
5454

5555
describe('hooks tests', () => {
5656
describe('useQuery', () => {
57+
let getRenderCount: () => number = () => 0;
58+
59+
test('useQuery hook basic render count assumptions', async () => {
60+
function User() {
61+
getRenderCount = useRenderCounter();
62+
63+
const { isFetching } = api.endpoints.getUser.useQuery(1);
64+
65+
return (
66+
<div>
67+
<div data-testid="isFetching">{String(isFetching)}</div>
68+
</div>
69+
);
70+
}
71+
72+
render(<User />, { wrapper: storeRef.wrapper });
73+
expect(getRenderCount()).toBe(2); // By the time this runs, the initial render will happen, and the query will start immediately running by the time we can expect this
74+
75+
await waitFor(() => expect(screen.getByTestId('isFetching').textContent).toBe('false'));
76+
expect(getRenderCount()).toBe(3);
77+
});
78+
5779
test('useQuery hook sets isFetching=true whenever a request is in flight', async () => {
5880
function User() {
81+
getRenderCount = useRenderCounter();
5982
const [value, setValue] = React.useState(0);
6083

6184
const { isFetching } = api.endpoints.getUser.useQuery(1, { skip: value < 1 });
@@ -69,14 +92,18 @@ describe('hooks tests', () => {
6992
}
7093

7194
render(<User />, { wrapper: storeRef.wrapper });
95+
expect(getRenderCount()).toBe(1);
7296

7397
await waitFor(() => expect(screen.getByTestId('isFetching').textContent).toBe('false'));
74-
fireEvent.click(screen.getByText('Increment value'));
98+
fireEvent.click(screen.getByText('Increment value')); // setState = 1, perform request = 2
7599
await waitFor(() => expect(screen.getByTestId('isFetching').textContent).toBe('true'));
76100
await waitFor(() => expect(screen.getByTestId('isFetching').textContent).toBe('false'));
101+
expect(getRenderCount()).toBe(4);
102+
77103
fireEvent.click(screen.getByText('Increment value'));
78104
// Being that nothing has changed in the args, this should never fire.
79105
expect(screen.getByTestId('isFetching').textContent).toBe('false');
106+
expect(getRenderCount()).toBe(5); // even though there was no request, the button click updates the state so this is an expected render
80107
});
81108

82109
test('useQuery hook sets isLoading=true only on initial request', async () => {
@@ -114,6 +141,7 @@ describe('hooks tests', () => {
114141
let refetchMe: () => void = () => {};
115142
function User() {
116143
const [value, setValue] = React.useState(0);
144+
getRenderCount = useRenderCounter();
117145

118146
const { isLoading, isFetching, refetch } = api.endpoints.getUser.useQuery(22, { skip: value < 1 });
119147
refetchMe = refetch;
@@ -127,33 +155,33 @@ describe('hooks tests', () => {
127155
}
128156

129157
render(<User />, { wrapper: storeRef.wrapper });
158+
expect(getRenderCount()).toBe(1);
130159

131-
await waitFor(() => {
132-
expect(screen.getByTestId('isLoading').textContent).toBe('false');
133-
expect(screen.getByTestId('isFetching').textContent).toBe('false');
134-
});
135-
fireEvent.click(screen.getByText('Increment value'));
160+
expect(screen.getByTestId('isLoading').textContent).toBe('false');
161+
expect(screen.getByTestId('isFetching').textContent).toBe('false');
162+
163+
fireEvent.click(screen.getByText('Increment value')); // renders: set state = 1, perform request = 2
136164
// Condition is met, should load
137165
await waitFor(() => {
138166
expect(screen.getByTestId('isLoading').textContent).toBe('true');
139167
expect(screen.getByTestId('isFetching').textContent).toBe('true');
140168
});
169+
141170
// Make sure the request is done for sure.
142171
await waitFor(() => {
143172
expect(screen.getByTestId('isLoading').textContent).toBe('false');
144173
expect(screen.getByTestId('isFetching').textContent).toBe('false');
145174
});
175+
expect(getRenderCount()).toBe(4);
176+
146177
fireEvent.click(screen.getByText('Increment value'));
147-
// Being that we already have data, isLoading should be false
148-
await waitFor(() => {
149-
expect(screen.getByTestId('isLoading').textContent).toBe('false');
150-
expect(screen.getByTestId('isFetching').textContent).toBe('false');
151-
});
152-
// Make sure the request is done for sure.
178+
// Being that we already have data and changing the value doesn't trigger a new request, only the button click should impact the render
153179
await waitFor(() => {
154180
expect(screen.getByTestId('isLoading').textContent).toBe('false');
155181
expect(screen.getByTestId('isFetching').textContent).toBe('false');
156182
});
183+
expect(getRenderCount()).toBe(5);
184+
157185
// We call a refetch, should set both to true, then false when complete/errored
158186
act(() => refetchMe());
159187
await waitFor(() => {
@@ -164,6 +192,7 @@ describe('hooks tests', () => {
164192
expect(screen.getByTestId('isLoading').textContent).toBe('false');
165193
expect(screen.getByTestId('isFetching').textContent).toBe('false');
166194
});
195+
expect(getRenderCount()).toBe(7);
167196
});
168197

169198
test('useQuery hook respects refetchOnMountOrArgChange: true', async () => {
@@ -372,10 +401,11 @@ describe('hooks tests', () => {
372401
data = undefined;
373402
});
374403

404+
let getRenderCount: () => number = () => 0;
375405
test('useLazyQuery does not automatically fetch when mounted and has undefined data', async () => {
376406
function User() {
377407
const [fetchUser, { data: hookData, isFetching, isUninitialized }] = api.endpoints.getUser.useLazyQuery();
378-
408+
getRenderCount = useRenderCounter();
379409
data = hookData;
380410

381411
return (
@@ -391,17 +421,22 @@ describe('hooks tests', () => {
391421
}
392422

393423
render(<User />, { wrapper: storeRef.wrapper });
424+
expect(getRenderCount()).toBe(1);
394425

395426
await waitFor(() => expect(screen.getByTestId('isUninitialized').textContent).toBe('true'));
396427
await waitFor(() => expect(data).toBeUndefined());
397428

398429
fireEvent.click(screen.getByTestId('fetchButton'));
430+
expect(getRenderCount()).toBe(2);
399431

400-
await waitFor(() => {
401-
expect(screen.getByTestId('isUninitialized').textContent).toBe('false');
402-
expect(screen.getByTestId('isFetching').textContent).toBe('true');
403-
});
432+
await waitFor(() => expect(screen.getByTestId('isUninitialized').textContent).toBe('false'));
404433
await waitFor(() => expect(screen.getByTestId('isFetching').textContent).toBe('false'));
434+
expect(getRenderCount()).toBe(3);
435+
436+
fireEvent.click(screen.getByTestId('fetchButton'));
437+
await waitFor(() => expect(screen.getByTestId('isFetching').textContent).toBe('true'));
438+
await waitFor(() => expect(screen.getByTestId('isFetching').textContent).toBe('false'));
439+
expect(getRenderCount()).toBe(5);
405440
});
406441

407442
test('useLazyQuery accepts updated subscription options and only dispatches updateSubscriptionOptions when values are updated', async () => {
@@ -411,6 +446,7 @@ describe('hooks tests', () => {
411446
const [fetchUser, { data: hookData, isFetching, isUninitialized }] = api.endpoints.getUser.useLazyQuery(
412447
options
413448
);
449+
getRenderCount = useRenderCounter();
414450

415451
data = hookData;
416452

@@ -437,30 +473,35 @@ describe('hooks tests', () => {
437473
}
438474

439475
render(<User />, { wrapper: storeRef.wrapper });
476+
expect(getRenderCount()).toBe(1); // hook mount
440477

441478
await waitFor(() => expect(screen.getByTestId('isUninitialized').textContent).toBe('true'));
442479
await waitFor(() => expect(data).toBeUndefined());
443480

444481
fireEvent.click(screen.getByTestId('fetchButton'));
482+
expect(getRenderCount()).toBe(2);
445483

446484
await waitFor(() => expect(screen.getByTestId('isFetching').textContent).toBe('true'));
447485
await waitFor(() => expect(screen.getByTestId('isFetching').textContent).toBe('false'));
486+
expect(getRenderCount()).toBe(3);
448487

449-
fireEvent.click(screen.getByTestId('updateOptions'));
450-
fireEvent.click(screen.getByTestId('fetchButton'));
488+
fireEvent.click(screen.getByTestId('updateOptions')); // setState = 1
489+
expect(getRenderCount()).toBe(4);
451490

491+
fireEvent.click(screen.getByTestId('fetchButton')); // perform new request = 2
452492
await waitFor(() => expect(screen.getByTestId('isFetching').textContent).toBe('true'));
453493
await waitFor(() => expect(screen.getByTestId('isFetching').textContent).toBe('false'));
494+
expect(getRenderCount()).toBe(6);
454495

455496
interval = 1000;
456497

457-
fireEvent.click(screen.getByTestId('updateOptions'));
458-
fireEvent.click(screen.getByTestId('fetchButton'));
498+
fireEvent.click(screen.getByTestId('updateOptions')); // setState = 1
499+
expect(getRenderCount()).toBe(7);
459500

460-
await waitFor(() => {
461-
expect(screen.getByTestId('isFetching').textContent).toBe('true');
462-
});
501+
fireEvent.click(screen.getByTestId('fetchButton'));
502+
await waitFor(() => expect(screen.getByTestId('isFetching').textContent).toBe('true'));
463503
await waitFor(() => expect(screen.getByTestId('isFetching').textContent).toBe('false'));
504+
expect(getRenderCount()).toBe(9);
464505

465506
expect(
466507
storeRef.store.getState().actions.filter(api.internalActions.updateSubscriptionOptions.match)
@@ -534,7 +575,7 @@ describe('hooks tests', () => {
534575
});
535576

536577
describe('useMutation', () => {
537-
test('useMutation hook sets and unsets the `isLoading` flag when running', async () => {
578+
test('useMutation hook sets and unsets the isLoading flag when running', async () => {
538579
function User() {
539580
const [updateUser, { isLoading }] = api.endpoints.updateUser.useMutation();
540581

@@ -1043,6 +1084,7 @@ describe('hooks with createApi defaults set', () => {
10431084
</div>,
10441085
{ wrapper: storeRef.wrapper }
10451086
);
1087+
10461088
expect(screen.getByTestId('renderCount').textContent).toBe('1');
10471089

10481090
const addBtn = screen.getByTestId('addPost');

test/helpers.tsx

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { AnyAction, configureStore, EnhancedStore, Middleware, Store } from '@re
22
import { setupListeners } from '@rtk-incubator/rtk-query';
33

44
import { act } from '@testing-library/react-hooks';
5-
import React, { Reducer } from 'react';
5+
import React, { Reducer, useCallback } from 'react';
66
import { Provider } from 'react-redux';
77

88
export const ANY = 0 as any;
@@ -42,6 +42,22 @@ export const hookWaitFor = async (cb: () => void, time = 2000) => {
4242
}
4343
};
4444

45+
export const useRenderCounter = () => {
46+
const countRef = React.useRef(0);
47+
48+
React.useEffect(() => {
49+
countRef.current += 1;
50+
});
51+
52+
React.useEffect(() => {
53+
return () => {
54+
countRef.current = 0;
55+
};
56+
}, []);
57+
58+
return useCallback(() => countRef.current, []);
59+
};
60+
4561
export function matchSequence(_actions: AnyAction[], ...matchers: Array<(arg: any) => boolean>) {
4662
const actions = _actions.concat();
4763
actions.shift(); // remove INIT

0 commit comments

Comments
 (0)