- Notifications
You must be signed in to change notification settings - Fork4
A library to make commited-render-to-committed-render assertions on your React components and hooks.
License
testing-library/react-render-stream-testing-library
Folders and files
| Name | Name | Last commit message | Last commit date | |
|---|---|---|---|---|
Repository files navigation
This library allows you to make committed-render-to-committed-render assertionson your React components and hooks. This is usually not necessary, but can behighly beneficial when testing hot code paths.
This library is intended to test libraries or library-like code. It requires youto write additional components so you can test how your components interact withother components in specific scenarios.
As such, it is not intended to be used for end-to-end testing of yourapplication.
This library originally was part of the Apollo Client test suite and ismaintained by the Apollo Client team.
If used withsnapshotDOM, RSTL will create a snapshot of your DOM after everyrender, and you can iterate through all the intermediate states of your DOM atyour own pace, independenly of how fast these renders actually happened.
test('iterate through renders with DOM snapshots',async()=>{const{takeRender, render}=createRenderStream({snapshotDOM:true,})constutils=awaitrender(<Counter/>)constincrementButton=utils.getByText('Increment')awaituserEvent.click(incrementButton)awaituserEvent.click(incrementButton){const{withinDOM}=awaittakeRender()constinput=withinDOM().getByLabelText('Value')expect(input.value).toBe('0')}{const{withinDOM}=awaittakeRender()constinput=withinDOM().getByLabelText('Value')expect(input.value).toBe('1')}{const{withinDOM}=awaittakeRender()constinput=withinDOM().getByLabelText('Value')expect(input.value).toBe('2')}})
Usage is very similar to RTL'srenderHook, but you get asnapshotStreamobject back that you can iterate withtakeSnapshot calls.
test('`useQuery` with `skip`',async()=>{const{takeSnapshot, rerender}=awaitrenderHookToSnapshotStream(({skip})=>useQuery(query,{skip}),{wrapper:({children})=><Providerclient={client}>{children}</Provider>,},){constresult=awaittakeSnapshot()expect(result.loading).toBe(true)expect(result.data).toBe(undefined)}{constresult=awaittakeSnapshot()expect(result.loading).toBe(false)expect(result.data).toEqual({hello:'world 1'})}awaitrerender({skip:true}){constsnapshot=awaittakeSnapshot()expect(snapshot.loading).toBe(false)expect(snapshot.data).toEqual(undefined)}})
You can track if a component was rerendered during a specific render by callinguseTrackRenders within it.
test('`useTrackRenders` with suspense',async()=>{functionErrorComponent(){useTrackRenders()// return ...}functionDataComponent(){useTrackRenders()constdata=useSuspenseQuery(someQuery)// return ...}functionLoadingComponent(){useTrackRenders()// return ...}functionApp(){useTrackRenders()return(<ErrorBoundaryFallbackComponent={ErrorComponent}><React.Suspensefallback={<LoadingComponent/>}><DataComponent/></React.Suspense></ErrorBoundary>)}const{takeRender, render}=createRenderStream()awaitrender(<App/>){const{renderedComponents}=awaittakeRender()expect(renderedComponents).toEqual([App,LoadingComponent])}{const{renderedComponents}=awaittakeRender()expect(renderedComponents).toEqual([DataComponent])}})
Note
The order of components inrenderedComponents is the order of execution ofuseLayoutEffect. Keep in mind that this might not be the order you wouldexpect.
If you need to, you can also take custom snapshots of data in each render.
test('custom snapshots with `replaceSnapshot`',async()=>{functionCounter(){const[value,setValue]=React.useState(0)replaceSnapshot({value})// return ...}const{takeRender, replaceSnapshot, render}=createRenderStream<{value:number}>()constutils=awaitrender(<Counter/>)constincrementButton=utils.getByText('Increment')awaituserEvent.click(incrementButton){const{snapshot}=awaittakeRender()expect(snapshot).toEqual({value:0})}{const{snapshot}=awaittakeRender()expect(snapshot).toEqual({value:1})}})
Tip
replaceSnapshot can also be called with a callback that gives you access tothe last snapshot value.
Tip
You can also usemergeSnapshot, which shallowly merges the last snapshotwith the new one instead of replacing it.
test('assertions in `onRender`',async()=>{functionCounter(){const[value,setValue]=React.useState(0)replaceSnapshot({value})return(<CounterFormvalue={value}onIncrement={()=>setValue(v=>v+1)}/>)}const{takeRender, replaceSnapshot, utils}=awaitrenderToRenderStream<{value:number}>({onRender(info){// you can use `expect` hereexpect(info.count).toBe(info.snapshot.value+1)},})constincrementButton=utils.getByText('Increment')awaituserEvent.click(incrementButton)awaituserEvent.click(incrementButton)awaittakeRender()awaittakeRender()awaittakeRender()})
Note
info contains thebase profiling informationpassed intoonRender of React'sProfiler component, as well assnapshot,replaceSnapshot andmergeSnapshot
This library adds to matchers toexpect that can be used like
test('basic functionality',async()=>{const{takeRender}=awaitrenderToRenderStream(<RerenderingComponent/>)awaitexpect(takeRender).toRerender()awaittakeRender()// trigger a rerender somehowawaitexpect(takeRender).toRerender()awaittakeRender()// ensure at the end of a test that no more renders will happenawaitexpect(takeRender).not.toRerender()awaitexpect(takeRender).toRenderExactlyTimes(2)})
These matchers can be used on multiple different objects:
awaitexpect(takeRender).toRerender()awaitexpect(renderStream).toRerender()awaitexpect(takeSnapshot).toRerender()awaitexpect(snapshotStream).toRerender()
Note
By default,.toRerender andtoRenderExactlyTimes will wait 100ms forrenders or to ensure no more renders happens.
You can modify that with thetimeout option:
awaitexpect(takeRender).not.toRerender({timeout:300})
Tip
If you don't want these matchers not to be automatically installed, you canimport from@testing-library/react-render-stream/pure instead.
Keep in mind that if you use the/pure import, you have to call thecleanup export manually after each test.
Usage side-by side with@testing-library/react or other tools that useact or setIS_REACT_ACT_ENVIRONMENT
This library should not be used withact, and it will throw an error ifIS_REACT_ACT_ENVIRONMENT istrue.
React Testing Library setsIS_REACT_ACT_ENVIRONMENT totrue globally, andwraps some helpers likeuserEvent.click inact calls.
To use this library side-by-side with React Testing Library, we ship thedisableActEnvironment helper to undo these changes temporarily.
It returns aDisposable and can be used together with theusing keywordto automatically clean up once the scope is left:
test('my test',()=>{ using_disabledAct=disableActEnvironment()// your test code here// as soon as this scope is left, the environment will be cleaned up})
If you cannot useusing, you can also manually call the returnedcleanupfunction. We recommend usingfinally to ensure the act environment is cleanedup if your test fails, otherwise it could leak between tests:
test('my test',()=>{const{cleanup}=disableActEnvironment()try{// your test code here}finally{cleanup()}})
About
A library to make commited-render-to-committed-render assertions on your React components and hooks.
Resources
License
Code of conduct
Uh oh!
There was an error while loading.Please reload this page.
Stars
Watchers
Forks
Uh oh!
There was an error while loading.Please reload this page.