Skip to content

Commit 28becfd

Browse files
authored
Merge pull request #32 from sectsect/feature/test-on-error
test: add testing for response 500 error
2 parents 3ccba4c + 5a65cdf commit 28becfd

10 files changed

Lines changed: 438 additions & 53 deletions

File tree

apps/solidjs-boilerplate/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@
6161
"husky": "^8.0.3",
6262
"jsdom": "^24.0.0",
6363
"lint-staged": "^15.2.0",
64+
"msw": "^2.3.1",
6465
"postcss": "^8.4.33",
6566
"postcss-calc": "^9.0.1",
6667
"postcss-combine-duplicated-selectors": "^10.0.3",

apps/solidjs-boilerplate/src/components/modules/PostList/PostList.test.tsx

Lines changed: 122 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,47 @@ import { MetaProvider } from '@solidjs/meta';
44
import { Route, Router } from '@solidjs/router';
55
import { render, renderHook, screen, waitFor } from '@solidjs/testing-library';
66
import { QueryClient, QueryClientProvider } from '@tanstack/solid-query';
7-
import { describe, test, beforeEach } from 'vitest';
7+
import { HttpResponse, http } from 'msw';
8+
import toast from 'solid-toast';
9+
import { describe, expect, test, vi } from 'vitest';
810

911
import PostList from '@/components/modules/PostList/PostList';
1012
import useFetchPostList from '@/hooks/useFetchPostList';
13+
import { server } from '@/mocks/server';
14+
15+
const apiEndpointUrl = import.meta.env.VITE_PUBLIC_API_URL;
16+
17+
vi.mock('solid-toast', () => ({
18+
default: {
19+
error: vi.fn(),
20+
},
21+
}));
1122

1223
describe('PostList component', () => {
13-
const queryClient = new QueryClient();
24+
// const queryClient = new QueryClient({
25+
// defaultOptions: { queries: { retry: false } },
26+
// });
27+
28+
// beforeEach(() => {
29+
// render(() => <PostList />, {
30+
// wrapper: props => (
31+
// <MetaProvider>
32+
// <QueryClientProvider client={queryClient}>
33+
// <Router
34+
// // eslint-disable-next-line @typescript-eslint/no-shadow
35+
// root={props => <>{props.children}</>}
36+
// >
37+
// <Route path="/" component={() => <>{props.children}</>} />
38+
// </Router>
39+
// </QueryClientProvider>
40+
// </MetaProvider>
41+
// ),
42+
// });
43+
// });
44+
45+
test('should render "Loading..." element before Data Fetching', async () => {
46+
const queryClient = new QueryClient();
1447

15-
beforeEach(() => {
1648
render(() => <PostList />, {
1749
wrapper: props => (
1850
<MetaProvider>
@@ -27,9 +59,7 @@ describe('PostList component', () => {
2759
</MetaProvider>
2860
),
2961
});
30-
});
3162

32-
test('should render "Loading..." element before Data Fetching', async () => {
3363
// screen.getByRole('button', { name: '' });
3464
const loadingEle = screen.getByText('Loading...'); // substring match
3565
expect(loadingEle).toBeInTheDocument();
@@ -40,6 +70,23 @@ describe('PostList component', () => {
4070
// });
4171

4272
test('should success to fetch data', async () => {
73+
const queryClient = new QueryClient();
74+
75+
render(() => <PostList />, {
76+
wrapper: props => (
77+
<MetaProvider>
78+
<QueryClientProvider client={queryClient}>
79+
<Router
80+
// eslint-disable-next-line @typescript-eslint/no-shadow
81+
root={props => <>{props.children}</>}
82+
>
83+
<Route path="/" component={() => <>{props.children}</>} />
84+
</Router>
85+
</QueryClientProvider>
86+
</MetaProvider>
87+
),
88+
});
89+
4390
// expect(await screen.findByText(/qui est esse/)).toBeInTheDocument();
4491

4592
// @ https://github.com/testing-library/react-hooks-testing-library/issues/23#issuecomment-477542354
@@ -63,14 +110,77 @@ describe('PostList component', () => {
63110
await waitFor(() => {
64111
expect(result.data?.length).toBe(100);
65112
});
113+
});
114+
115+
test('should handle API call failure', async () => {
116+
const errorMessage = 'Failed to fetch data.';
117+
const toastErrorSpy = vi.spyOn(toast, 'error');
118+
119+
server.use(
120+
http.get(
121+
`${apiEndpointUrl}/posts`,
122+
() =>
123+
new HttpResponse(null, {
124+
status: 500,
125+
}),
126+
),
127+
);
128+
129+
const queryClient = new QueryClient({
130+
defaultOptions: { queries: { retry: false } }, // @ https://github.com/TanStack/query/discussions/2300#discussioncomment-811768
131+
});
66132

67-
// await waitFor(async () => {
68-
// if (!result.isSuccess) {
69-
// throw Error('wait');
70-
// }
133+
render(() => <PostList />, {
134+
wrapper: props => (
135+
<MetaProvider>
136+
<QueryClientProvider client={queryClient}>
137+
<Router
138+
// eslint-disable-next-line @typescript-eslint/no-shadow
139+
root={props => <>{props.children}</>}
140+
>
141+
<Route path="/" component={() => <>{props.children}</>} />
142+
</Router>
143+
</QueryClientProvider>
144+
</MetaProvider>
145+
),
146+
});
147+
148+
const wrapper = (props: { children: JSX.Element }) => (
149+
<QueryClientProvider client={queryClient}>
150+
<Router
151+
// eslint-disable-next-line @typescript-eslint/no-shadow
152+
root={props => <>{props.children}</>}
153+
>
154+
<Route
155+
path="/"
156+
component={() => (
157+
<>
158+
{props.children}
159+
{/* <Toaster position="bottom-center" /> */}
160+
</>
161+
)}
162+
/>
163+
</Router>
164+
</QueryClientProvider>
165+
);
166+
167+
// screen.getByRole('');
168+
const { result } = renderHook(() => useFetchPostList(), {
169+
wrapper,
170+
});
171+
172+
await waitFor(() => expect(result.isError).toBe(true));
173+
174+
await waitFor(() => {
175+
expect(result.error).toEqual(Error('Failed to fetch data'));
176+
});
177+
178+
await waitFor(() => {
179+
expect(toastErrorSpy).toHaveBeenCalledWith(errorMessage);
180+
});
71181

72-
// // expect(await screen.findByText(/qui est esse/)).toBeInTheDocument();
73-
// expect(screen.getByText(/qui est esse/)).toBeInTheDocument();
74-
// });
182+
// expect(
183+
// await screen.findByText('Failed to fetch data.'),
184+
// ).toBeInTheDocument();
75185
});
76186
});

apps/solidjs-boilerplate/src/components/modules/PostList/PostList.tsx

Lines changed: 14 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,21 @@
11
import type { Component } from 'solid-js';
2-
import { Switch, Match, For } from 'solid-js';
2+
import { ErrorBoundary, Suspense, For, createEffect } from 'solid-js';
33

44
import { A } from '@solidjs/router';
5-
import { createQuery } from '@tanstack/solid-query';
65
import toast from 'solid-toast';
76

87
import Head from '@/components/modules/Head/Head';
9-
import type { Post } from '@/types/api/post.interface';
10-
11-
const apiEndpointUrl = import.meta.env.VITE_PUBLIC_API_URL;
12-
13-
const fetchData = async (): Promise<Post[]> =>
14-
(await fetch(`${apiEndpointUrl}/posts`)).json();
8+
import useFetchPostList from '@/hooks/useFetchPostList';
159

1610
const PostList: Component = () => {
1711
// w/ createQuery() on '@tanstack/solid-query'
18-
const query = createQuery(() => ({
19-
queryKey: ['/posts'],
20-
queryFn: fetchData,
21-
}));
12+
const query = useFetchPostList();
2213

23-
if (query.isError) {
24-
toast.error('Failed to fetch data.');
25-
}
14+
createEffect(() => {
15+
if (query.isError) {
16+
toast.error('Failed to fetch data.');
17+
}
18+
});
2619

2720
// w/ createResource()
2821
// const [posts, { refetch }] = createResource<Post[]>(fetchData);
@@ -37,14 +30,10 @@ const PostList: Component = () => {
3730

3831
<section>
3932
<div class="inner">
40-
<Switch>
41-
<Match when={query.isLoading}>
42-
<div>Loading...</div>
43-
</Match>
44-
<Match when={query.isError}>
45-
<div>Error: {(query.error as Error).message}</div>
46-
</Match>
47-
<Match when={query.isSuccess}>
33+
<ErrorBoundary
34+
fallback={<div>Error: {(query.error as Error).message}</div>}
35+
>
36+
<Suspense fallback={<div>Loading...</div>}>
4837
<ul>
4938
<For each={query.data}>
5039
{post => (
@@ -54,8 +43,8 @@ const PostList: Component = () => {
5443
)}
5544
</For>
5645
</ul>
57-
</Match>
58-
</Switch>
46+
</Suspense>
47+
</ErrorBoundary>
5948
</div>
6049
</section>
6150

apps/solidjs-boilerplate/src/hooks/useFetchPostList.tsx

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,30 @@
1+
import type { CreateQueryResult } from '@tanstack/solid-query';
12
import { createQuery } from '@tanstack/solid-query';
23

34
import type { Post } from '@/types/api/post.interface';
45

6+
// import type { Post } from '@/types/api/post.interface';
7+
58
const apiEndpointUrl = import.meta.env.VITE_PUBLIC_API_URL;
69

7-
const fetchData = async (): Promise<Post[]> =>
8-
(await fetch(`${apiEndpointUrl}/posts`)).json();
10+
// const fetchData = async (): Promise<Post[]> =>
11+
// (await fetch(`${apiEndpointUrl}/posts`)).json();
912

1013
const useFetchPostList = () => {
11-
const query = createQuery(() => ({
14+
// const query = createQuery(() => ({
15+
// queryKey: ['/posts'],
16+
// queryFn: fetchData,
17+
// }));
18+
19+
const query: CreateQueryResult<Post[], Error> = createQuery(() => ({
1220
queryKey: ['/posts'],
13-
queryFn: fetchData,
21+
queryFn: async () => {
22+
const result = await fetch(`${apiEndpointUrl}/posts`);
23+
if (!result.ok) throw new Error('Failed to fetch data');
24+
return result.json();
25+
},
26+
staleTime: 1000 * 60 * 5, // 5 minutes
27+
throwOnError: true, // Throw an error if the query fails
1428
}));
1529

1630
return query;
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// eslint-disable-next-line import/no-extraneous-dependencies
2+
import { http, HttpResponse } from 'msw';
3+
4+
export const handlers = [
5+
// Intercept "GET https://example.com/user" requests...
6+
http.get('https://example.com/user', () => {
7+
// ...and respond to them using this JSON response.
8+
return HttpResponse.json({
9+
id: 'c7b3d8e0-5e0b-4b0f-8b3a-3b9f4b3d3b3d',
10+
firstName: 'John',
11+
lastName: 'Maverick',
12+
});
13+
}),
14+
];
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// eslint-disable-next-line import/no-extraneous-dependencies
2+
import { setupServer } from 'msw/node';
3+
4+
import { handlers } from './handlers';
5+
6+
export const server = setupServer(...handlers);

apps/solidjs-boilerplate/vite.config.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,25 @@ export default defineConfig({
3535
all: false,
3636
provider: 'v8',
3737
reporter: ['text', 'json', 'html', 'lcov'],
38+
exclude: [
39+
'coverage/**',
40+
'dist/**',
41+
'**/[.]**',
42+
'packages/*/test?(s)/**',
43+
'**/*.d.ts',
44+
'**/virtual:*',
45+
'**/__x00__*',
46+
'**/\x00*',
47+
'cypress/**',
48+
'test?(s)/**',
49+
'test?(-*).?(c|m)[jt]s?(x)',
50+
'**/*{.,-}{test,spec}?(-d).?(c|m)[jt]s?(x)',
51+
'**/__tests__/**',
52+
'**/{karma,rollup,webpack,vite,vitest,jest,ava,babel,nyc,cypress,tsup,build}.config.*',
53+
'**/vitest.{workspace,projects}.[jt]s?(on)',
54+
'**/.{eslint,mocha,prettier}rc.{?(c|m)js,yml}',
55+
'**/mocks/**',
56+
],
3857
},
3958
// solid needs to be inline to work around
4059
// a resolution issue in vitest:
@@ -52,6 +71,7 @@ export default defineConfig({
5271
'**/.{idea,git,cache,output,temp}/**',
5372
'**/{karma,rollup,webpack,vite,vitest,jest,ava,babel,nyc,cypress}.config.*',
5473
'**/e2e/**', // Additional e2e directory for Playwright.
74+
'**/mocks/**',
5575
],
5676
deps: {}, // @ https://qiita.com/Stead08/items/762093fe86999fec4e80
5777
},
Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,10 @@
1-
// eslint-disable-next-line import/no-extraneous-dependencies
2-
import '@testing-library/jest-dom';
1+
/* eslint-disable import/no-extraneous-dependencies */
2+
import '@testing-library/jest-dom/vitest';
3+
import { beforeAll, afterEach, afterAll } from 'vitest';
4+
5+
import { server } from './src/mocks/server';
6+
7+
// So this is basically saying any network requests that come through route them to mock service worker instead of the actual network.
8+
beforeAll(() => server.listen());
9+
afterEach(() => server.resetHandlers());
10+
afterAll(() => server.close());
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
// eslint-disable-next-line import/no-extraneous-dependencies
2-
import '@testing-library/jest-dom';
2+
import '@testing-library/jest-dom/vitest';

0 commit comments

Comments
 (0)