-
Notifications
You must be signed in to change notification settings - Fork 144
Migrate Enzyme tests to React Testing Library #5299
Conversation
7de2c4d
to
6fd97cf
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well done @jeffstieler ! It looks great and I haven't noticed anything critical 🎉
Suggested alternative patterns in some places. They are not mandatory but IMO lead to better readability and tests output in case of failure. Also note, I've suggested most of alternative patterns in a single place but noticed they might be applied in many other places.
Hope that helps 🙂
expect( | ||
queryByText( 'No data recorded for the selected time period.' ) | ||
).not.toBeNull(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Couple of things here:
- It's better use get query as it gives better output in case of error. More guides on queries are here.
- There is
toBeInTheDocument
matcher which makes more sense. This one is general and applies to many places.
expect( firstRow[ 2 ].value ).toBe( mockData[ 0 ].orders_count ); | ||
expect( firstRow[ 3 ].value ).toBe( | ||
formatDecimal( mockData[ 0 ].net_revenue ) | ||
const leaderboard = container.querySelector( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not familiar with context and not sure how is important to check specific values, but we could replace the whole checks below with snapshot, expect ( container ).toMatchSnapshot()
. What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In my first pass through these tests I was hesitant to change their behavior too much. That said, introducing snapshot testing where we are just checking markup seems perfectly reasonable. Thanks for the nudge!
test( 'should set the time-comparison mode prop by default', () => { | ||
const reportChart = mount( | ||
<ReportChart | ||
path={ path } | ||
primaryData={ data } | ||
query={ {} } | ||
secondaryData={ data } | ||
selectedChart={ selectedChart } | ||
/> | ||
); | ||
const chart = reportChart.find( 'Chart' ); | ||
|
||
expect( chart.props().mode ).toEqual( 'time-comparison' ); | ||
} ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This test doesn't change DOM but only internal state of the component, does it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe that DOM changes would result from this test, but they would be in the nested <Chart>
component, not the <ReportChart>
that is under test here.
I removed this test case because it felt really low value. It only covers the default case here:
woocommerce-admin/client/analytics/components/report-chart/index.js
Lines 355 to 358 in 7232d51
const chartMode = | |
props.mode || | |
getChartMode( selectedFilter, query ) || | |
'time-comparison'; |
expect( summaryNumber.props().prevValue ).toBe( '500.25' ); | ||
expect( summaryNumber.props().delta ).toBe( 100 ); | ||
} ); | ||
expect( screen.getByText( '1,000.5' ) ).not.toBeEmptyDOMElement(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm, this check looks unclear to me, but I can miss some details here.
It's recommended to use query*
selector for such cases, because get should throw if element/text is not found.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think replacing .not.toBeEmptyDOMElement()
with .toBeInTheDocument()
will make this test more clear. getBy*
was chosen because the expectation is that the text can be found - the test should fail if it cannot be.
expect( | ||
container.getElementsByClassName( 'components-spinner' ) | ||
).toHaveLength( 0 ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here and in other places for better readability:
expect( | |
container.getElementsByClassName( 'components-spinner' ) | |
).toHaveLength( 0 ); | |
expect( | |
container.querySelector( '.components-spinner' ) | |
).not.toBeInTheDocument(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What do you think of:
expect(
container.querySelector( '.components-spinner' )
).toBeNull();
querySelector()
returns null
when the selector can't be found.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It will work, but iirc the output is more helpful when not.toBeInTheDocument()
is used. I'd suggest breaking the test and compare the output. It's fine to go with any option you like more :)
const { getByText } = render( <SetupNotice isSetupError={ true } /> ); | ||
expect( | ||
getByText( | ||
'Unable to set up the plugin. Refresh the page and try again.' | ||
) | ||
).toHaveClass( 'wc-admin-shipping-banner-install-error' ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it be better to match snapshot here? Class name by itself doesn't look like a good indicator that the error is displayed. I mean, snapshot test will catch both text change and class name change and it'll be easier to manage the test.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I defer to @c-shultz here - I just ported team Heisenberg's tests.
expect( | ||
document.getElementById( 'woocommerce-admin-print-label' ).style | ||
.display | ||
).toBe( 'none' ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
expect( | ||
container.querySelector( '.wc-admin-shipping-banner-blob p' ) | ||
.textContent | ||
).toBe( activatedMessage ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it be sufficient checking the text only here? Or check for the class name is essential?
expect( | |
container.querySelector( '.wc-admin-shipping-banner-blob p' ) | |
.textContent | |
).toBe( activatedMessage ); | |
expect( | |
getByText( activatedMessage ) | |
).toBeInTheDocument(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also deferring to @c-shultz here - I just ported team Heisenberg's tests.
expect( | ||
container.getElementsByClassName( 'woocommerce-card' ) | ||
).toHaveLength( 1 ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is already snapshot matcher above, so I'd say this check is not necessary. If you still consider it as important, I'd recommend changing it into expect( container.querySelector( '.woocommerce-card' ) ).toBeInTheDocument();
to have better output in case of error.
expect( | ||
getByRole( 'option', { name: 'lorem 1' } ) | ||
).not.toBeEmptyDOMElement(); | ||
expect( | ||
getByRole( 'option', { name: 'lorem 2' } ) | ||
).not.toBeEmptyDOMElement(); | ||
expect( | ||
getByRole( 'option', { name: 'bar' } ) | ||
).not.toBeEmptyDOMElement(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not critical, but iterating over options makes sense to me here: options.forEach( option => expect( getByRole( 'option', { name: option.label } ) ).toBeInTheDocument() );
@vbelolapotkov thank you for the thorough review! (I was only expecting folks on different teams to check the code their team added 😅 ) I'm going through your feedback now. Thanks again. |
I wish I understand this before 😜 But hope the feedback was helpful 😄 Good luck!
👍 you're welcome! |
Thanks again for the review @vbelolapotkov! I've addressed most of the feedback, and pinged other folks who can weigh in on what's important to test. If this is looking good though, I think we should get it merged in before there are too many conflicts. 🙃 |
Hey @jeffstieler , looks great! I'd just been experimenting with The only thing I'll note is that large snapshots such as That said, looks good! ✔️ |
@joelclimbsthings good point re snapshots. I think we should aim to remove snapshot testing altogether because I don't think we're getting tonnes of value from it. That said, I'd be happy to see that addressed later. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks so much for this @jeffstieler 🚢
Remove tests that inspect DOM since there are snapshots.
9a102e7
to
2aedc93
Compare
* Migrate leaderboard tests to RTL. * Remove test of default prop value. * Migrate ReportSummary tests to RTL. * Migrate ActivityCard tests to RTL. * Migrate ActivityCardPlaceholder tests to RTL. * Migrate remaining ProductType tests to RTL. * Migrate Card tests to RTL. * Update RTL and user event packages. * Migrate Date tests to RTL. * Migrate D3Legend tests to RTL. * Migrate D3Base tests to RTL. * Migrate Gravatar tests to RTL. * Migrate ImageUpload tests to RTL. * Migrate ProductImage tests to RTL. * Migrate Rating tests to RTL. * Migrate Search tests to RTL. * Migrate Plugins tests to RTL. * Migrate SelectControl tests to RTL. * Migrate Timeline tests to RTL. Remove tests that inspect DOM since there are snapshots. * Migrate DismissModal tests to RTL. * Migrate SetupNotice tests to RTL. * Migrate WelcomeCard tests to RTL. * Fix setup error reason retrieval in ShippingBanner. * Migrate ShippingBanner tests to RTL. * Migrate RecommendedExtensions tests to RTL. * Migrate KnowledgeBase tests to RTL. * Rename enzyme setup file, modify to setup RTL. * No need to import jest-dom in test files. * Remove enzyme dependency. * Use snapshot for testing Leaderboard markup. * Switch from "not to be empty" to "be in the document". * No need to waitFor() recordEvent mock. * Be specific about clicking the "hide" button. * Use toBeVisible() instead of checking style property.
This PR migrates all remaining Enzyme tests to React Testing Library.
Where reasonable, it also changes the test behavior to check user facing functionality rather than component props or specific HTML elements.
Heads up to @c-shultz @jconroy @allendav - this PR changes test code from your teams. Please have someone review!
Detailed test instructions:
Changelog Note:
Dev: Remove Enzyme in favor of React Testing Library