I'm still in the process of moving my Enzyme tests over to react-testing-library, and I have a fairly common scenario where, when a component mounts, it kicks off an Ajax request to get some data. Just before the fetch starts, it sets some state value to indicate it is loading, which in turn renders a spinner. When it's finished, the state is updated with both the data, and "loadingState" is set to "Completed" or "Failed", where appropriate.
import React, { useEffect, useState } from "react";
import { SwapSpinner } from "react-spinners-kit";
import styled from "styled-components";
import * as R from "ramda";
import { getPeople } from "./getPeople";
const FlexCenter = styled.div`
height: 250px;
display: flex;
justify-content: center;
align-items: center;
`;
const loadingStates = {
notStarted: "notStarted",
isLoading: "isLoading",
success: "success",
failure: "failure"
};
function App() {
const [people, setPeople] = useState([]);
const [isLoading, setLoading] = useState(loadingStates.notStarted);
useEffect(() => {
setLoading(loadingStates.isLoading);
getPeople()
.then(({ results }) => {
setPeople(results);
setLoading(loadingStates.success);
})
.catch(error => {
setLoading(loadingStates.failure);
});
}, []);
return (
<div>
{R.cond([
[
R.equals(loadingStates.isLoading),
() => (
<FlexCenter data-testid="spinner">
<SwapSpinner />
</FlexCenter>
)
],
[
R.equals(loadingStates.success),
() => (
<ul data-testid="people-list">
{people.map(({ name }) => (
<li key={name}>{name}</li>
))}
</ul>
)
],
[R.equals(loadingStates.failure), <div>An error occured</div>]
])(isLoading)}
</div>
);
}
export default App;
With Enzyme, I could manually set the state to any one of the loadingStates
keys, and assert that the render conditional renders the appropriate changes.
Is there a way that I can do this in RTL?
You can click the button which will set the val to true. it('small container visible only when val is true', () => { const {queryByText, getByTestId} = render(<MyComponent />); const clickButton = getByTestId('your.button.test.id'); fireEvent.
createEvent[eventName]
You can not do that with RTL. You are not supposed to interact with the internals of your components.
This is roughly how I would test your component:
import { getPeople } from "./getPeople";
jest.mock('./getPeople')
test('skeleton of a test', async () => {
const people = [/* Put some mock people in here */]
getPeople.mockResolvedValueOnce({ results: people })
render(<App />)
expect(/* Somehow get the loading spinner */).toBeInTheDocument()
await wait(() => expect(/* Here you check that the people is on the page */).toBeInTheDocument())
// We also check that the API gets called
expect(getPeople).toHaveBeenCalledOnce()
expect(getPeople).toHaveBeenCalledWith()
})
As you can see, I'm not checking what's the internal state of App
. Instead, I'm checking that a loading spinner is shown and that after that the people appear on the screen and that the API gets called.
This test is more reliable because you're testing what a user would see and not the implementation details.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With