Skip to content

Commit cb3b543

Browse files
dggsaxphryneas
andauthored
Add test cases for basic example project (reduxjs#1218)
Co-authored-by: Lenz Weber <mail@lenzw.de>
1 parent 40ad269 commit cb3b543

File tree

9 files changed

+135
-11
lines changed

9 files changed

+135
-11
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,4 @@ typesversions
2929

3030
.yalc
3131
yalc.lock
32-
yalc.sig
32+
yalc.sig

examples/query/react/basic/package.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,12 @@
1313
"react-scripts": "4.0.2"
1414
},
1515
"devDependencies": {
16+
"@testing-library/react": "^12.0.0",
17+
"@types/jest": "^26.0.23",
1618
"@types/react": "17.0.0",
1719
"@types/react-dom": "17.0.0",
1820
"@types/react-redux": "7.1.9",
21+
"msw": "^0.30.0",
1922
"typescript": "~4.2.4"
2023
},
2124
"eslintConfig": {
@@ -28,7 +31,8 @@
2831
},
2932
"scripts": {
3033
"start": "react-scripts start",
31-
"build": "react-scripts build"
34+
"build": "react-scripts build",
35+
"test": "react-scripts test --runInBand"
3236
},
3337
"browserslist": [
3438
">0.2%",
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { rest } from 'msw'
2+
import { screen } from '@testing-library/react'
3+
4+
import App from './App'
5+
import { server } from './test/server'
6+
import { renderWithProviders } from './test/test-utils'
7+
8+
describe('App', () => {
9+
it('handles good response', async () => {
10+
renderWithProviders(<App />)
11+
12+
screen.getByText('Loading...')
13+
14+
await screen.findByRole('heading', { name: /bulbasaur/i })
15+
16+
const img = screen.getByRole('img', {
17+
name: /bulbasaur/i,
18+
}) as HTMLImageElement
19+
20+
expect(img.src).toBe(
21+
'https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/shiny/1.png'
22+
)
23+
})
24+
25+
it('handles error response', async () => {
26+
// force msw to return error response
27+
server.use(
28+
rest.get(
29+
'https://pokeapi.co/api/v2/pokemon/bulbasaur',
30+
(req, res, ctx) => {
31+
return res(ctx.status(500))
32+
}
33+
)
34+
)
35+
36+
renderWithProviders(<App />)
37+
38+
screen.getByText('Loading...')
39+
40+
await screen.findByText('Oh no, there was an error')
41+
})
42+
})

examples/query/react/basic/src/index.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ import { render } from 'react-dom'
22
import { Provider } from 'react-redux'
33

44
import App from './App'
5-
import { store } from './store'
5+
import { setupStore } from './store'
6+
7+
const store = setupStore()
68

79
const rootElement = document.getElementById('root')
810
render(
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { server } from './test/server'
2+
3+
// make debug output for TestingLibrary Errors larger
4+
process.env.DEBUG_PRINT_LIMIT = '15000'
5+
6+
// enable API mocking in test runs using the same request handlers
7+
// as for the client-side mocking.
8+
beforeAll(() => server.listen({ onUnhandledRequest: 'error' }))
9+
afterAll(() => server.close())
10+
afterEach(() => server.resetHandlers())
Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,21 @@
1-
import { configureStore } from '@reduxjs/toolkit'
1+
import { combineReducers, configureStore } from '@reduxjs/toolkit'
2+
import type { PreloadedState } from '@reduxjs/toolkit'
23
import { pokemonApi } from './services/pokemon'
34

4-
export const store = configureStore({
5-
reducer: {
6-
[pokemonApi.reducerPath]: pokemonApi.reducer,
7-
},
8-
// adding the api middleware enables caching, invalidation, polling and other features of `rtk-query`
9-
middleware: (getDefaultMiddleware) =>
10-
getDefaultMiddleware().concat(pokemonApi.middleware),
5+
const rootReducer = combineReducers({
6+
[pokemonApi.reducerPath]: pokemonApi.reducer,
117
})
8+
9+
export const setupStore = (preloadedState?: PreloadedState<RootState>) => {
10+
return configureStore({
11+
reducer: rootReducer,
12+
middleware: (getDefaultMiddleware) =>
13+
// adding the api middleware enables caching, invalidation, polling and other features of `rtk-query`
14+
getDefaultMiddleware().concat(pokemonApi.middleware),
15+
preloadedState,
16+
})
17+
}
18+
19+
export type RootState = ReturnType<typeof rootReducer>
20+
export type AppStore = ReturnType<typeof setupStore>
21+
export type AppDispatch = AppStore['dispatch']
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { setupServer } from 'msw/node'
2+
import { handlers } from './serverHandlers'
3+
4+
const server = setupServer(...handlers)
5+
6+
export { server }
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { rest } from 'msw'
2+
3+
const handlers = [
4+
rest.get('https://pokeapi.co/api/v2/pokemon/bulbasaur', (req, res, ctx) => {
5+
const mockApiResponse = {
6+
species: {
7+
name: 'bulbasaur',
8+
},
9+
sprites: {
10+
front_shiny:
11+
'https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/shiny/1.png',
12+
},
13+
}
14+
return res(ctx.json(mockApiResponse))
15+
}),
16+
]
17+
18+
export { handlers }
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { render } from '@testing-library/react'
2+
import type { RenderOptions } from '@testing-library/react'
3+
import React, { PropsWithChildren } from 'react'
4+
import { Provider } from 'react-redux'
5+
import { setupStore } from '../store'
6+
import type { AppStore, RootState } from '../store'
7+
import type { PreloadedState } from '@reduxjs/toolkit'
8+
9+
// This type interface extends the default options for render from RTL, as well
10+
// as allows the user to specify other things such as initialState, store. For
11+
// future dependencies, such as wanting to test with react-router, you can extend
12+
// this interface to accept a path and route and use those in a <MemoryRouter />
13+
interface ExtendedRenderOptions extends Omit<RenderOptions, 'queries'> {
14+
preloadedState?: PreloadedState<RootState>
15+
store?: AppStore
16+
}
17+
18+
function renderWithProviders(
19+
ui: React.ReactElement,
20+
{
21+
preloadedState = {},
22+
store = setupStore(preloadedState),
23+
...renderOptions
24+
}: ExtendedRenderOptions = {}
25+
) {
26+
function Wrapper({ children }: PropsWithChildren<{}>): JSX.Element {
27+
return <Provider store={store}>{children}</Provider>
28+
}
29+
return { store, ...render(ui, { wrapper: Wrapper, ...renderOptions }) }
30+
}
31+
32+
export { renderWithProviders }

0 commit comments

Comments
 (0)