Reputation: 771
I test a react component that should display either spinner, error or a list of fetched recipes. I managed to test this component for loading and when it successfully get data. I have a problem with testing error. I created test server with msw, that has route handler that returns error. I use axios to make requests to the server. I think the problem is here: axios makes 3 requests and until last response useQuery returns isLoading=true, isError=false. Only after that it returns isLoading=false, isError=true. So in my test for error screen.debug() shows spinner, and errorMessage returns error because it does not find a rendered element with text 'error', that component is supposed to show when error occured. What can I do about that? I run out of ideas.
EDIT:
I'm new to react testing and Typescript.
From RecipeList.test.tsx:
import { setupServer } from 'msw/node';
import { rest } from 'msw';
import { QueryClient, QueryClientProvider, QueryCache } from 'react-query';
import RecipeList from './RecipeList';
const server = setupServer(
rest.get(`${someUrl}`, (req, res, ctx) =>
res(ctx.status(200), ctx.json(data))
)
);
const queryCache = new QueryCache();
const RecipeListWithQueryClient = () => {
const client = new QueryClient();
return (
<QueryClientProvider client={client}>
<RecipeList />
</QueryClientProvider>
);
};
// Tests setup
beforeAll(() => server.listen());
afterAll(() => server.close());
afterEach(() => server.resetHandlers());
afterEach(() => {
queryCache.clear();
});
describe('RecipeList', () => {
//some tests that pass
describe('while error', () => {
it('display error message', async () => {
server.use(
rest.get(`${someUrl}`, (req, res, ctx) => res(ctx.status(404)))
);
render(<RecipeListWithQueryClient />);
// FOLLOWING CODE RAISE ERROR
const errorMessage = await screen.findByText(/error/i);
await expect(errorMessage).toBeInTheDocument();
});
});
});
From RecipeList.tsx:
import { fetchRecipes } from 'api';
const RecipeList: React.FC = () => {
const { data, isLoading, isError } = useQuery<
RecipeDataInterface,
Error
>('recipes', fetchRecipes);;
if (isLoading) {
return (
<Spinner>
<span className="sr-only">Loading...</span>
</Spinner>
);
}
if (isError) {
return (
<Alert >Error occured. Please try again later</Alert>
);
}
return (
<>
{data?.records.map((recipe: RecipeInterface) => (
<RecipeItem key={recipe.id} recipe={recipe.fields} />
))}
</>
);
};
Upvotes: 1
Views: 14369
Reputation: 545
I used QueryClient to disable retry. And setupServer to mock network connection
import { QueryClient, QueryClientProvider} from 'react-query';
import { setupServer } from 'msw/node'
import { rest } from 'msw'
const server = setupServer(
rest.get('https://reqres.in/api/users', (req, res, ctx) => {
return res(
ctx.status(200),
ctx.json(userSuccessResponseJson()
),
)
}),)
beforeAll(() => server.listen())
afterEach(() => {
server.resetHandlers();
queryClient.clear();
})
afterAll(() => server.close())
const queryClient = new QueryClient({
defaultOptions: {
queries: {
retryDelay: 1,
retry:0,
},
},
})
const Wrapper = ({ children }) => (
<QueryClientProvider client={queryClient}>
{children}
</QueryClientProvider>
);
test('Renders Error text when backend server response with error', async () => {
server.use(
rest.get('https://reqres.in/api/users', (req, res, ctx) => {
return res(ctx.status(403))
})
);
render(<Wrapper><CompanyList />)</Wrapper>);
const linkElement = screen.getByText(/Loading/i);
expect(linkElement).toBeInTheDocument();
await waitFor(() => {
const fetchedText = screen.getByText(/Error:/i);
expect(fetchedText).toBeInTheDocument();
})
});
Upvotes: 14
Reputation: 771
The solution I found is to set individual timeout value for the test.
In RTL for waitFor async method according to docs :
"The default timeout is 1000ms which will keep you under Jest's default timeout of 5000ms."
I had to set timeout option for RTL async function, but it was not enough as I was getting an error:
"Exceeded timeout of 5000 ms for a test.Use jest.setTimeout(newTimeout) to increase the timeout value, if this is a long-running test." I had to change timeout value set by Jest for the test. It can be done in two ways as described in this comment:
" 1. Add a third parameter to the it. I.e., it('runs slow', () => {...}, 10000) 2. Write jest.setTimeout(10000); in a file named src/setupTests.js, as specified here."
Finally my test looks like this:
it('display error message', async () => {
server.use(
rest.get(getUrl('/recipes'), (req, res, ctx) =>
res(ctx.status(500), ctx.json({ errorMessage: 'Test Error' }))
)
);
render(<RecipeListWithQueryClient />);
expect(
await screen.findByText(/error/i, {}, { timeout: 10000 })
).toBeInTheDocument();
}, 10000);
Different async methods could be used here like waitFor and waitForElementToBeRemoved and they all can take { timeout: 10000 } object as an argument.
This solution works for me, however I would like to improve it, to avoid the tests taking so much time. I just didn't figure out yet how to do it.
Upvotes: -2