diff --git a/config/jest.config.json b/config/jest.config.json index 74e8fd4..d1b8134 100644 --- a/config/jest.config.json +++ b/config/jest.config.json @@ -18,5 +18,8 @@ "setupFilesAfterEnv": ["/config/jest.setup.js"], "testMatch": [ "/tests/**/?(*.)(test).js" ], "moduleFileExtensions": ["js", "json", "ts", "tsx"], - "reporters": [ "default", "jest-junit" ] + "reporters": [ "default", "jest-junit" ], + "moduleNameMapper": { + "axios": "axios/dist/node/axios.cjs" + } } diff --git a/package-lock.json b/package-lock.json index 5d54340..2d58550 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3815,6 +3815,30 @@ "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", "dev": true }, + "axios": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.2.2.tgz", + "integrity": "sha512-bz/J4gS2S3I7mpN/YZfGFTqhXTYzRho8Ay38w2otuuDR322KzFIWm/4W2K6gIwvWaws5n+mnb7D1lN9uD+QH6Q==", + "dev": true, + "requires": { + "follow-redirects": "^1.15.0", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + }, + "dependencies": { + "form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dev": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } + } + } + }, "babel-jest": { "version": "24.9.0", "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-24.9.0.tgz", @@ -5224,6 +5248,12 @@ "locate-path": "^3.0.0" } }, + "follow-redirects": { + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", + "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", + "dev": true + }, "for-each": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", @@ -11399,6 +11429,12 @@ "react-is": "^16.8.1" } }, + "proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "dev": true + }, "psl": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", diff --git a/package.json b/package.json index 81ecc53..38a0395 100644 --- a/package.json +++ b/package.json @@ -53,6 +53,7 @@ "@types/history": "^4.7.9", "@types/jest": "^26.0.23", "@types/react": "^17.0.16", + "axios": "^1.2.2", "babel-jest": "^24.8.0", "jest": "^27.0.4", "jest-junit": "^6.4.0", diff --git a/src/mockNetwork.ts b/src/mockNetwork.ts index 7ecac81..d4d55aa 100644 --- a/src/mockNetwork.ts +++ b/src/mockNetwork.ts @@ -5,17 +5,61 @@ import { getRequestMatcher } from './requestMatcher' declare global { interface Window { fetch: jest.Mock + XMLHttpRequest: jest.Mock } } beforeEach(() => { global.window.fetch = jest.fn() + global.window.XMLHttpRequest = jest.fn() as jest.MockedClass< + typeof global.window.XMLHttpRequest + > }) afterEach(() => { global.window.fetch.mockRestore() + global.window.XMLHttpRequest.mockRestore() }) +class XHRMock { + responses: Response[] = [] + method: string = '' + url: string = '' + status: number = 200 + readyState: number = 0 + response: any | undefined + _onreadystatechange: () => void = () => null + + constructor(responses: Response[]) { + this.responses = responses + } + + open(method: string, url: string) { + this.method = method + this.url = url + } + + send() { + const request = { + method: this.method, + url: this.url, + } + + const responseMatchingRequest = this.responses.find( + getRequestMatcher(request), + ) + + this.status = responseMatchingRequest?.status || 200 + this.response = responseMatchingRequest?.responseBody + this.readyState = 4 + this._onreadystatechange() + } + + set onreadystatechange(fn: () => void) { + this._onreadystatechange = fn + } +} + const createDefaultResponse = async () => { const response = { json: () => Promise.resolve(), @@ -102,6 +146,9 @@ const mockNetwork = (responses: Response[] = [], debug: boolean = false) => { const request = input return mockFetch(responses, request, debug) }) + + const XMLHttpRequest = global.window.XMLHttpRequest + XMLHttpRequest.mockImplementation(() => new XHRMock(responses)) } const printMultipleResponsesWarning = (response: Response) => { diff --git a/tests/components.mock.js b/tests/components.mock.js index 49d3ed5..1b5dbed 100644 --- a/tests/components.mock.js +++ b/tests/components.mock.js @@ -1,3 +1,4 @@ +import axios from 'axios/lib/axios' import React, { Component, Fragment, useState, useEffect } from 'react' import { createPortal } from 'react-dom' import { Provider, useDispatch, useSelector } from 'react-redux' diff --git a/tests/withNetwork.xhr.test.js b/tests/withNetwork.xhr.test.js new file mode 100644 index 0000000..0771189 --- /dev/null +++ b/tests/withNetwork.xhr.test.js @@ -0,0 +1,49 @@ +import React from 'react' +import axios from 'axios' +import { render, screen, fireEvent } from '@testing-library/react' +import { wrap, configure } from '../src/index' + +it('should have network by default', async () => { + configure({ mount: render }) + wrap(() =>
).withNetwork([ + { path: '/foo/bar', responseBody: { foo: 'bar' }} + ]).mount() + const mockedFn = jest.fn() + let xhr = new XMLHttpRequest() + xhr.open('GET', '/foo/bar') + xhr.onreadystatechange = mockedFn + xhr.send() + + expect(xhr.response).toEqual({ foo: 'bar' }) + expect(xhr.status).toEqual(200) + expect(mockedFn).toHaveBeenCalled() +}) + +it('should have network by default', async () => { + configure({ mount: render }) + wrap(() =>
).withNetwork([ + { path: '/bar/foo', responseBody: { bar: 'foo' }}, + { path: '/foo/bar', responseBody: { foo: 'bar' }}, + ]).mount() + + const firstResponse = await axios.get('/foo/bar', { responseType: 'application/json' }) + const secondResponse = await axios.get('/bar/foo', { responseType: 'application/json' }) + + expect(firstResponse.data).toEqual({ foo: 'bar' }) + expect(secondResponse.data).toEqual({ bar: 'foo' }) +}) + +it('should handle failed axios requests', async () => { + configure({ mount: render }) + wrap(() =>
).withNetwork([ + { path: '/bar/foo', responseBody: { bar: 'foo' }, status: 400}, + ]).mount() + + try { + await axios.get('/bar/foo', { responseType: 'application/json' }) + } catch (error) { + expect(error.name).toBe('AxiosError') + expect(error.message).toBe('Request failed with status code 400') + expect(error.response.data).toEqual({ bar: 'foo' }) + } +}) \ No newline at end of file