Last active
August 3, 2023 13:47
-
-
Save SchabaJo/b012605f6d5d6f5b0fb99ea4e8de5b10 to your computer and use it in GitHub Desktop.
jest-openapi + openapi-fetch
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import innerCreateClient, { | |
FetchResponse as InnerFetchResponse, | |
HttpMethod, | |
PathsWith, | |
FetchOptions, | |
FilterKeys, | |
MediaType, | |
} from 'openapi-fetch' | |
type FetchResponse<T, M extends HttpMethod> = T extends { responses: unknown } | |
? keyof T['responses'] extends infer R | |
? R extends keyof T['responses'] | |
? { | |
req: { path: string; method: Uppercase<M> } | |
status: R | |
body: NonNullable<FilterKeys<FilterKeys<T['responses'][R], 'content'>, MediaType>> | |
} | |
: never | |
: never | |
: never | |
type ClientMethods = Exclude<HttpMethod, 'delete'> | 'del' | |
type FetchClient<Paths extends NonNullable<unknown>> = { | |
[Method in ClientMethods]: Method extends HttpMethod | |
? <P extends PathsWith<Paths, Method>>( | |
url: P, | |
init: FetchOptions<FilterKeys<Paths[P], Method>>, | |
) => Promise< | |
FetchResponse< | |
Method extends infer T | |
? T extends Method | |
? T extends keyof Paths[P] | |
? Paths[P][T] | |
: unknown | |
: never | |
: never, | |
Method | |
> | |
> | |
: <P extends PathsWith<Paths, 'delete'>>( | |
url: P, | |
init: FetchOptions<FilterKeys<Paths[P], 'delete'>>, | |
) => Promise< | |
FetchResponse< | |
'delete' extends infer T | |
? T extends 'delete' | |
? T extends keyof Paths[P] | |
? Paths[P][T] | |
: unknown | |
: never | |
: never, | |
'delete' | |
> | |
> | |
} | |
function parseResponse<Paths extends NonNullable<unknown>, M extends HttpMethod>( | |
rawResponse: InnerFetchResponse<Paths>, | |
method: Uppercase<M>, | |
): FetchResponse<Paths, M> { | |
return { | |
req: { | |
path: rawResponse.response.url, | |
method, | |
}, | |
status: rawResponse.response.status, | |
body: rawResponse.data || rawResponse.error, | |
} as FetchResponse<Paths, M> | |
} | |
export function createClient<Paths extends NonNullable<unknown>>( | |
clientOptions: Parameters<typeof innerCreateClient>[0], | |
): FetchClient<Paths> { | |
const { get, put, post, del, options, head, patch, trace } = | |
innerCreateClient<Paths>(clientOptions) | |
return { | |
get: async (url, init) => parseResponse(await get(url, init), 'GET'), | |
put: async (url, init) => parseResponse(await put(url, init), 'PUT'), | |
post: async (url, init) => parseResponse(await post(url, init), 'POST'), | |
del: async (url, init) => parseResponse(await del(url, init), 'DELETE'), | |
options: async (url, init) => parseResponse(await options(url, init), 'OPTIONS'), | |
head: async (url, init) => parseResponse(await head(url, init), 'HEAD'), | |
patch: async (url, init) => parseResponse(await patch(url, init), 'PATCH'), | |
trace: async (url, init) => parseResponse(await trace(url, init), 'TRACE'), | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { resolve } from 'node:path' | |
import SwaggerParser from '@apidevtools/swagger-parser' | |
import jestOpenAPI from 'jest-openapi' | |
import { createClient } from './client' | |
import type { paths } from './openapi' | |
const { get } = createClient<paths>({ baseUrl: 'https://api.example.com/v1' }) | |
beforeAll(async () => { | |
// jest-openapi does not resolve external refs, use @apidevtools/swagger-parser instead | |
jestOpenAPI(await SwaggerParser.validate(resolve(__dirname, 'openapi.yaml'))) | |
}) | |
describe('Example API', () => { | |
it('should do something', async () => { | |
const response = await get('/endpoint') | |
expect(response).toSatisfyApiSpec() | |
}) | |
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import * as undici from 'undici' | |
declare global { | |
export const { fetch, FormData, Headers, Request, Response }: typeof undici | |
type FormData = undici.FormData | |
type Headers = undici.Headers | |
type HeadersInit = undici.HeadersInit | |
type BodyInit = undici.BodyInit | |
type Request = undici.Request | |
type RequestInit = undici.RequestInit | |
type RequestInfo = undici.RequestInfo | |
type RequestMode = undici.RequestMode | |
type RequestRedirect = undici.RequestRedirect | |
type RequestCredentials = undici.RequestCredentials | |
type RequestDestination = undici.RequestDestination | |
type ReferrerPolicy = undici.ReferrerPolicy | |
type Response = undici.Response | |
type ResponseInit = undici.ResponseInit | |
type ResponseType = undici.ResponseType | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This wrapper for
openapi-fetch
turns its response into asuperagent
-like response compatible withjest-openapi
.It relies on
openapi-typescript
to generate types from OpenAPIopenapi-fetch
to setup a properly typed client@apidevtools/swagger-parser
to load OpenAPI files with support for external$ref
sjest-openapi
to ease testing using jestundici
to get types not supported by@types/node
It is also (hopefully properly) typed, so following code is possible: