Skip to content

Commit

Permalink
Proper mock fetch implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
3c1u committed Apr 28, 2021
1 parent 820bb48 commit 6d5a390
Show file tree
Hide file tree
Showing 3 changed files with 126 additions and 27 deletions.
134 changes: 122 additions & 12 deletions tests/mock/fetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,26 +14,43 @@ export const breeds: Breeds = {
tachibana: ['asane', 'junnosuke'],
}

export const mockBreedsServerResponse = {
message: breeds,
status: 'success',
export const imageByBreeds: Record<
string,
string[] | Record<string, string[]>
> = {
katagiri: [],
watarai: [],
hotori: [],
kotoyose: [],
reizeiin: [imageUrl],
onabuta: [],
tadasugawa: [],
susuko: [],
tachibana: {
asane: [],
junnosuke: [],
},
}

export const mockServerResponse = {
message: imageUrl,
status: 'success',
const statusTextByCode: Record<number, string> = {
200: 'OK',
404: 'Not Found',
}

export const mockResponse: Response = {
const mockResponse = (
url: string,
serverResponse: any,
code?: number,
): Response => ({
headers: {} as any,
ok: true,
redirected: false,
status: 200,
statusText: 'OK',
status: code ?? 200,
statusText: statusTextByCode[code ?? 200] ?? 'OK',
trailer: {} as any,
type: 'default',
body: null,
url: '',
url,
bodyUsed: false,
arrayBuffer() {
throw new Error('unimplemented; You are doing something wrong.')
Expand All @@ -45,12 +62,105 @@ export const mockResponse: Response = {
throw new Error('unimplemented; You are doing something wrong.')
},
json() {
return Promise.resolve(JSON.parse(JSON.stringify(mockServerResponse)))
return Promise.resolve(JSON.parse(JSON.stringify(serverResponse)))
},
text() {
return Promise.resolve(JSON.stringify(mockServerResponse))
return Promise.resolve(JSON.stringify(serverResponse))
},
clone() {
throw new Error('unimplemented; You are doing something wrong.')
},
})

const randomImageTest = /^(https?)?:\/\/dog.ceo\/api\/breeds\/image\/random\/?$/
const randomImageTestByBreed = /^(https?)?:\/\/dog.ceo\/api\/breed\/([A-Za-z]+)(\/([A-Za-z]+))?\/image\/random\/?$/
const breedsAllTest = /^(https?)?:\/\/dog.ceo\/api\/breeds\/all\/?$/

// not used
// const randomMultipleImageTest = /^(https?)?:\/\/dog.ceo\/api\/breeds\/image\/random\/([1-9]*[0-9])\/?$/
// const breedImagesTest = /^(https?)?:\/\/dog.ceo\/api\/breed\/([A-Za-z]+)\/images\/?$/
// const randomMultipleImageTestByBreed = /^(https?)?:\/\/dog.ceo\/api\/breed\/([A-Za-z]+)(\/([A-Za-z]+))?\/image\/random\/([1-9]*[0-9])\/?$/
// const breedListTest = /^(https?)?:\/\/dog.ceo\/api\/breed\/([A-Za-z]+)\/list\/?$/

const pickOne = <T>(a: T[]): T | undefined => {
if (a.length === 0) {
return undefined
}

return a[Math.floor(a.length * Math.random())]
}

const mockApiRoutes: {
test: RegExp
handle(url: string): { message: any; status: string; code?: number }
}[] = [
{
test: randomImageTest,
handle(_: string) {
return {
message: imageUrl,
status: 'success',
}
},
},
{
test: breedsAllTest,
handle(_: string) {
return {
message: breeds,
status: 'success',
}
},
},
{
test: randomImageTestByBreed,
handle(url: string) {
const [breed, _, subbreed] = url.match(randomImageTestByBreed)!.slice(3)

const breedImages = imageByBreeds[breed]
if (breedImages === undefined) {
return {
status: 'error',
message: 'Breed not found (master breed does not exist)',
code: 404,
}
}

if (subbreed) {
if (
breedImages instanceof Array ||
breedImages[subbreed] === undefined
) {
return {
status: 'error',
message: 'Breed not found (sub breed does not exist)',
code: 404,
}
}

return {
message: pickOne(breedImages[subbreed]),
status: 'success',
}
}

if (!(breedImages instanceof Array)) {
return {
message: pickOne(Object.values(breedImages).flat()),
status: 'success',
}
}

return {
message: pickOne(breedImages),
status: 'success',
}
},
},
]

export const fetchMock: typeof window.fetch = (resource, ..._) => {
const url = typeof resource === 'string' ? resource : resource.url
const res = mockApiRoutes.find(r => r.test.test(url))!.handle(url)
return Promise.resolve(mockResponse(url, res, res.code))
}
9 changes: 2 additions & 7 deletions tests/station06.test.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from 'react'
import renderer, { act } from 'react-test-renderer'
import { App } from '../src/App'
import { mockResponse, imageUrl } from './mock/fetch'
import { imageUrl, fetchMock } from './mock/fetch'

describe('<App />', () => {
const callback = {
Expand All @@ -13,12 +13,7 @@ describe('<App />', () => {

window.fetch = fetch

fetch.mockImplementation((resource, ..._) => {
const url = typeof resource === 'string' ? resource : resource.url
return Promise.resolve({
...mockResponse, url,
})
})
fetch.mockImplementation(fetchMock)

useStateSpy.mockImplementation((value?: unknown) => {
return [
Expand Down
10 changes: 2 additions & 8 deletions tests/station08.test.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,13 @@
import React from 'react'
import renderer, { act } from 'react-test-renderer'
import { mockResponse, imageUrl } from './mock/fetch'
import { imageUrl, fetchMock } from './mock/fetch'

describe('<App />', () => {
const fetch = jest.fn()

window.fetch = fetch

fetch.mockImplementation((resource, ..._) => {
const url = typeof resource === 'string' ? resource : resource.url
return Promise.resolve({
...mockResponse,
url,
})
})
fetch.mockImplementation(fetchMock)

afterEach(() => {
jest.clearAllMocks()
Expand Down

0 comments on commit 6d5a390

Please sign in to comment.