diff --git a/src/handlers/RequestHandler.ts b/src/handlers/RequestHandler.ts index 3d0e5bd10..3aacb8277 100644 --- a/src/handlers/RequestHandler.ts +++ b/src/handlers/RequestHandler.ts @@ -37,6 +37,7 @@ export type DefaultRequestMultipartBody = Record< export type DefaultBodyType = | Record | DefaultRequestMultipartBody + | FormData | string | number | boolean diff --git a/src/utils/request/MockedRequest.ts b/src/utils/request/MockedRequest.ts index 8d7d258d4..db7e828b3 100644 --- a/src/utils/request/MockedRequest.ts +++ b/src/utils/request/MockedRequest.ts @@ -108,6 +108,14 @@ export class MockedRequest< * to read the request body as a plain text, JSON, or ArrayBuffer. */ public get body(): RequestBody { + /** + * If an XHR sends a FormData body, as per https://developer.mozilla.org/fr/docs/Web/API/XMLHttpRequest/send the interceptor + * will pass the raw FormData instance directly, not an ArrayBuffer. Short-circuit here in this case + */ + if (this['_body'] instanceof FormData) { + return this['_body'] as RequestBody + } + const text = decodeBuffer(this['_body']) /** @@ -125,6 +133,10 @@ export class MockedRequest< return body as RequestBody } + public formData(): FormData { + return this['_body'] + } + /** * Bypass the intercepted request. * This will make a call to the actual endpoint requested. diff --git a/test/rest-api/request/body/body-form-data.node.test.ts b/test/rest-api/request/body/body-form-data.node.test.ts index 0620343b5..a69680b88 100644 --- a/test/rest-api/request/body/body-form-data.node.test.ts +++ b/test/rest-api/request/body/body-form-data.node.test.ts @@ -4,9 +4,41 @@ import { rest } from 'msw' import { setupServer } from 'msw/node' const server = setupServer( - rest.post('http://localhost/deprecated', (req, res, ctx) => { + rest.post('http://localhost/deprecated/json', (req, res, ctx) => { return res(ctx.json(req.body)) }), + rest.post('http://localhost/deprecated/formData', (req, res, ctx) => { + const body = req.body as FormData + const file = body.get('file') as File + + return res( + ctx.json({ + username: body.get('username'), + password: body.get('password'), + file: { + name: file.name, + size: file.size, + type: file.type, + }, + }), + ) + }), + rest.post('http://localhost/formData', (req, res, ctx) => { + const body = req.formData() + const file = body.get('file') as File + + return res( + ctx.json({ + username: body.get('username'), + password: body.get('password'), + file: { + name: file.name, + size: file.size, + type: file.type, + }, + }), + ) + }), ) beforeAll(() => { @@ -17,7 +49,7 @@ afterAll(() => { server.close() }) -test('handles "FormData" as a request body', async () => { +test('handles "FormData" from the "form-data" library as a request body', async () => { // Note that creating a `FormData` instance in Node/JSDOM differs // from the same instance in a real browser. Follow the instructions // of your `fetch` polyfill to learn more. @@ -25,7 +57,7 @@ test('handles "FormData" as a request body', async () => { formData.append('username', 'john.maverick') formData.append('password', 'secret123') - const res = await fetch('http://localhost/deprecated', { + const res = await fetch('http://localhost/deprecated/json', { method: 'POST', headers: formData.getHeaders(), body: formData, @@ -38,3 +70,51 @@ test('handles "FormData" as a request body', async () => { password: 'secret123', }) }) + +test.each([ + 'http://localhost/formData', + 'http://localhost/deprecated/formData', +])( + 'handles "FormData" native object as a request body when sent as an XHR request to %s', + async (url) => { + const formData = new FormData() + + formData.append('username', 'john.maverick') + formData.append('password', 'secret123') + formData.append( + 'file', + new Blob(['file content'], { type: 'text/plain' }), + 'file.txt', + ) + + await new Promise((resolve, reject) => { + const xhr = new XMLHttpRequest() + + xhr.open('POST', url, true) + + xhr.onerror = reject + xhr.onload = () => { + try { + const jsonResponse = JSON.parse(xhr.response) + + expect(xhr.status).toBe(200) + expect(jsonResponse).toEqual({ + username: 'john.maverick', + password: 'secret123', + file: { + name: 'file.txt', + size: 12, // Can't match file content exactly, no support for File.text() in jsdom + type: 'text/plain', + }, + }) + + resolve(void 0) + } catch (e) { + reject(e) + } + } + + xhr.send(formData) + }) + }, +)