├── .editorconfig ├── .github └── workflows │ └── node.js.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── eslint.config.js ├── package-lock.json ├── package.json ├── src ├── handle_request.js ├── index.js ├── is_blob.js └── utils.js ├── test ├── abort_request.spec.js ├── asymmetric.spec.js ├── basics.spec.js ├── cancelation.spec.js ├── default_instance.spec.js ├── history.spec.js ├── network_error.spec.js ├── on_any.spec.js ├── pass_through.spec.js ├── pass_through_on_no_match.spec.js ├── promise.spec.js ├── reply_once.spec.js ├── throw_exception_on_no_match.spec.js ├── timeout.spec.js ├── trailing_slash_baseurl.spec.js └── utils.spec.js ├── types ├── index.d.ts ├── test.ts └── tsconfig.json └── webpack.config.js /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | trim_trailing_whitespace = true 7 | indent_style = space 8 | indent_size = 2 9 | 10 | [*.md] 11 | trim_trailing_whitespace = false 12 | -------------------------------------------------------------------------------- /.github/workflows/node.js.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | 4 | name: Node.js CI 5 | 6 | on: 7 | push: 8 | branches: [ master ] 9 | pull_request: 10 | branches: [ master ] 11 | 12 | jobs: 13 | build: 14 | 15 | strategy: 16 | matrix: 17 | node-version: [18.x, 20.x, 22.x] 18 | axios-version: [0.17.0, latest] 19 | os: ['ubuntu-latest', 'macos-12'] 20 | # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ 21 | 22 | runs-on: ${{ matrix.os }} 23 | steps: 24 | - uses: actions/checkout@v3 25 | - name: Use Node.js ${{ matrix.node-version }} on ${{ matrix.os }} 26 | uses: actions/setup-node@v3 27 | with: 28 | node-version: ${{ matrix.node-version }} 29 | cache: 'npm' 30 | - run: npm ci 31 | - run: npm install axios@${{ matrix.axios-version }} 32 | - run: npm run build --if-present 33 | - run: npm test 34 | 35 | lint: 36 | runs-on: ubuntu-latest 37 | 38 | steps: 39 | - uses: actions/checkout@v3 40 | - name: ESLint 41 | uses: actions/setup-node@v3 42 | with: 43 | node-version: 22.x 44 | cache: 'npm' 45 | - run: npm ci 46 | - run: npm run build --if-present 47 | - run: npm run lint 48 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | npm-debug.log* 2 | node_modules 3 | dist 4 | coverage 5 | .nyc_output 6 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | This project follows [Semantic Versioning](http://semver.org/). 4 | Every release is documented on the GitHub [Releases](https://github.com/ctimmerm/axios-mock-adapter/releases) page. 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Colin Timmermans 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # axios-mock-adapter 2 | 3 | Axios adapter that allows to easily mock requests 4 | 5 | ## Installation 6 | 7 | Using npm: 8 | 9 | `$ npm install axios-mock-adapter --save-dev` 10 | 11 | It's also available as a UMD build: 12 | 13 | - https://unpkg.com/axios-mock-adapter/dist/axios-mock-adapter.js 14 | - https://unpkg.com/axios-mock-adapter/dist/axios-mock-adapter.min.js 15 | 16 | axios-mock-adapter works on Node as well as in a browser, it works with axios v0.17.0 and above. 17 | 18 | ## Example 19 | 20 | Mocking a `GET` request 21 | 22 | ```js 23 | const axios = require("axios"); 24 | const AxiosMockAdapter = require("axios-mock-adapter"); 25 | 26 | // This sets the mock adapter on the default instance 27 | const mock = new AxiosMockAdapter(axios); 28 | 29 | // Mock any GET request to /users 30 | // arguments for reply are (status, data, headers) 31 | mock.onGet("/users").reply(200, { 32 | users: [{ id: 1, name: "John Smith" }], 33 | }); 34 | 35 | axios.get("/users").then(function (response) { 36 | console.log(response.data); 37 | }); 38 | ``` 39 | 40 | Mocking a `GET` request with specific parameters 41 | 42 | ```js 43 | const axios = require("axios"); 44 | const AxiosMockAdapter = require("axios-mock-adapter"); 45 | 46 | // This sets the mock adapter on the default instance 47 | const mock = new AxiosMockAdapter(axios); 48 | 49 | // Mock GET request to /users when param `searchText` is 'John' 50 | // arguments for reply are (status, data, headers) 51 | mock.onGet("/users", { params: { searchText: "John" } }).reply(200, { 52 | users: [{ id: 1, name: "John Smith" }], 53 | }); 54 | 55 | axios 56 | .get("/users", { params: { searchText: "John" } }) 57 | .then(function (response) { 58 | console.log(response.data); 59 | }); 60 | ``` 61 | 62 | When using `params`, you must match _all_ key/value pairs passed to that option. 63 | 64 | To add a delay to responses, specify a delay amount (in milliseconds) when instantiating the adapter 65 | 66 | ```js 67 | // All requests using this instance will have a 2 seconds delay: 68 | const mock = new AxiosMockAdapter(axiosInstance, { delayResponse: 2000 }); 69 | ``` 70 | 71 | You can restore the original adapter (which will remove the mocking behavior) 72 | 73 | ```js 74 | mock.restore(); 75 | ``` 76 | 77 | You can also reset the registered mock handlers with `resetHandlers` 78 | 79 | ```js 80 | mock.resetHandlers(); 81 | ``` 82 | 83 | You can reset both registered mock handlers and history items with `reset` 84 | 85 | ```js 86 | mock.reset(); 87 | ``` 88 | 89 | `reset` is different from `restore` in that `restore` removes the mocking from the axios instance completely, 90 | whereas `reset` only removes all mock handlers that were added with onGet, onPost, etc. but leaves the mocking in place. 91 | 92 | Mock a low level network error 93 | 94 | ```js 95 | // Returns a failed promise with Error('Network Error'); 96 | mock.onGet("/users").networkError(); 97 | 98 | // networkErrorOnce can be used to mock a network error only once 99 | mock.onGet("/users").networkErrorOnce(); 100 | ``` 101 | 102 | Mock a network timeout 103 | 104 | ```js 105 | // Returns a failed promise with Error with code set to 'ECONNABORTED' 106 | mock.onGet("/users").timeout(); 107 | 108 | // timeoutOnce can be used to mock a timeout only once 109 | mock.onGet("/users").timeoutOnce(); 110 | ``` 111 | 112 | Passing a function to `reply` 113 | 114 | ```js 115 | mock.onGet("/users").reply(function (config) { 116 | // `config` is the axios config and contains things like the url 117 | 118 | // return an array in the form of [status, data, headers] 119 | return [ 120 | 200, 121 | { 122 | users: [{ id: 1, name: "John Smith" }], 123 | }, 124 | ]; 125 | }); 126 | ``` 127 | 128 | Passing a function to `reply` that returns an axios request, essentially mocking a redirect 129 | 130 | ```js 131 | mock.onPost("/foo").reply(function (config) { 132 | return axios.get("/bar"); 133 | }); 134 | ``` 135 | 136 | Using a regex 137 | 138 | ```js 139 | mock.onGet(/\/users\/\d+/).reply(function (config) { 140 | // the actual id can be grabbed from config.url 141 | 142 | return [200, {}]; 143 | }); 144 | ``` 145 | 146 | Using variables in regex 147 | 148 | ```js 149 | const usersUri = "/users"; 150 | const url = new RegExp(`${usersUri}/*`); 151 | 152 | mock.onGet(url).reply(200, users); 153 | ``` 154 | 155 | Specify no path to match by verb alone 156 | 157 | ```js 158 | // Reject all POST requests with HTTP 500 159 | mock.onPost().reply(500); 160 | ``` 161 | 162 | Chaining is also supported 163 | 164 | ```js 165 | mock.onGet("/users").reply(200, users).onGet("/posts").reply(200, posts); 166 | ``` 167 | 168 | `.replyOnce()` can be used to let the mock only reply once 169 | 170 | ```js 171 | mock 172 | .onGet("/users") 173 | .replyOnce(200, users) // After the first request to /users, this handler is removed 174 | .onGet("/users") 175 | .replyOnce(500); // The second request to /users will have status code 500 176 | // Any following request would return a 404 since there are 177 | // no matching handlers left 178 | ``` 179 | 180 | Mocking any request to a given url 181 | 182 | ```js 183 | // mocks GET, POST, ... requests to /foo 184 | mock.onAny("/foo").reply(200); 185 | ``` 186 | 187 | `.onAny` can be useful when you want to test for a specific order of requests 188 | 189 | ```js 190 | // Expected order of requests: 191 | const responses = [ 192 | ["GET", "/foo", 200, { foo: "bar" }], 193 | ["POST", "/bar", 200], 194 | ["PUT", "/baz", 200], 195 | ]; 196 | 197 | // Match ALL requests 198 | mock.onAny().reply((config) => { 199 | const [method, url, ...response] = responses.shift(); 200 | if (config.url === url && config.method.toUpperCase() === method) 201 | return response; 202 | // Unexpected request, error out 203 | return [500, {}]; 204 | }); 205 | ``` 206 | 207 | Requests that do not map to a mock handler are rejected with a HTTP 404 response. Since 208 | handlers are matched in order, a final `onAny()` can be used to change the default 209 | behaviour 210 | 211 | ```js 212 | // Mock GET requests to /foo, reject all others with HTTP 500 213 | mock.onGet("/foo").reply(200).onAny().reply(500); 214 | ``` 215 | 216 | Mocking a request with a specific request body/data 217 | 218 | ```js 219 | mock.onPut("/product", { id: 4, name: "foo" }).reply(204); 220 | ``` 221 | 222 | Using an asymmetric matcher, for example Jest matchers 223 | 224 | ```js 225 | mock 226 | .onPost( 227 | "/product", 228 | { id: 1 }, 229 | { 230 | headers: expect.objectContaining({ 231 | Authorization: expect.stringMatching(/^Basic /), 232 | }) 233 | } 234 | ) 235 | .reply(204); 236 | ``` 237 | 238 | Using a custom asymmetric matcher (any object that has a `asymmetricMatch` property) 239 | 240 | ```js 241 | mock 242 | .onPost("/product", { 243 | asymmetricMatch: function (actual) { 244 | return ["computer", "phone"].includes(actual["type"]); 245 | }, 246 | }) 247 | .reply(204); 248 | ``` 249 | 250 | `.passThrough()` forwards the matched request over network 251 | 252 | ```js 253 | // Mock POST requests to /api with HTTP 201, but forward 254 | // GET requests to server 255 | mock 256 | .onPost(/^\/api/) 257 | .reply(201) 258 | .onGet(/^\/api/) 259 | .passThrough(); 260 | ``` 261 | 262 | Recall that the order of handlers is significant 263 | 264 | ```js 265 | // Mock specific requests, but let unmatched ones through 266 | mock 267 | .onGet("/foo") 268 | .reply(200) 269 | .onPut("/bar", { xyz: "abc" }) 270 | .reply(204) 271 | .onAny() 272 | .passThrough(); 273 | ``` 274 | 275 | Note that `passThrough` requests are not subject to delaying by `delayResponse`. 276 | 277 | If you set `onNoMatch` option to `passthrough` all requests would be forwarded over network by default 278 | 279 | ```js 280 | // Mock all requests to /foo with HTTP 200, but forward 281 | // any others requests to server 282 | const mock = new AxiosMockAdapter(axiosInstance, { onNoMatch: "passthrough" }); 283 | 284 | mock.onAny("/foo").reply(200); 285 | ``` 286 | 287 | Using `onNoMatch` option with `throwException` to throw an exception when a request is made without match any handler. It's helpful to debug your test mocks. 288 | 289 | ```js 290 | const mock = new AxiosMockAdapter(axiosInstance, { onNoMatch: "throwException" }); 291 | 292 | mock.onAny("/foo").reply(200); 293 | 294 | axios.get("/unexistent-path"); 295 | 296 | // Exception message on console: 297 | // 298 | // Could not find mock for: 299 | // { 300 | // "method": "get", 301 | // "url": "http://localhost/unexistent-path" 302 | // } 303 | ``` 304 | 305 | As of 1.7.0, `reply` function may return a Promise: 306 | 307 | ```js 308 | mock.onGet("/product").reply(function (config) { 309 | return new Promise(function (resolve, reject) { 310 | setTimeout(function () { 311 | if (Math.random() > 0.1) { 312 | resolve([200, { id: 4, name: "foo" }]); 313 | } else { 314 | // reject() reason will be passed as-is. 315 | // Use HTTP error status code to simulate server failure. 316 | resolve([500, { success: false }]); 317 | } 318 | }, 1000); 319 | }); 320 | }); 321 | ``` 322 | 323 | Composing from multiple sources with Promises: 324 | 325 | ```js 326 | const normalAxios = axios.create(); 327 | const mockAxios = axios.create(); 328 | const mock = new AxiosMockAdapter(mockAxios); 329 | 330 | mock 331 | .onGet("/orders") 332 | .reply(() => 333 | Promise.all([ 334 | normalAxios.get("/api/v1/orders").then((resp) => resp.data), 335 | normalAxios.get("/api/v2/orders").then((resp) => resp.data), 336 | { id: "-1", content: "extra row 1" }, 337 | { id: "-2", content: "extra row 2" }, 338 | ]).then((sources) => [ 339 | 200, 340 | sources.reduce((agg, source) => agg.concat(source)), 341 | ]) 342 | ); 343 | ``` 344 | 345 | ## History 346 | 347 | The `history` property allows you to enumerate existing axios request objects. The property is an object of verb keys referencing arrays of request objects. 348 | 349 | This is useful for testing. 350 | 351 | ```js 352 | describe("Feature", () => { 353 | it("requests an endpoint", (done) => { 354 | const mock = new AxiosMockAdapter(axios); 355 | mock.onPost("/endpoint").replyOnce(200); 356 | 357 | feature 358 | .request() 359 | .then(() => { 360 | expect(mock.history.post.length).toBe(1); 361 | expect(mock.history.post[0].data).toBe(JSON.stringify({ foo: "bar" })); 362 | }) 363 | .then(done) 364 | .catch(done.fail); 365 | }); 366 | }); 367 | ``` 368 | 369 | You can clear the history with `resetHistory` 370 | 371 | ```js 372 | mock.resetHistory(); 373 | ``` 374 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | module.exports = [ 4 | { 5 | ignores: [ 6 | "dist/" 7 | ] 8 | }, 9 | { 10 | languageOptions: { 11 | ecmaVersion: 2022, 12 | sourceType: "commonjs" 13 | }, 14 | 15 | rules: { 16 | "brace-style": [2, "1tbs", { "allowSingleLine": false }], 17 | quotes: [ 18 | 2, 19 | "double", 20 | { 21 | avoidEscape: true, 22 | allowTemplateLiterals: true 23 | } 24 | ], 25 | "comma-dangle": [2, "only-multiline"], 26 | "curly": [2, "multi-line"], 27 | "eol-last": 2, 28 | "eqeqeq": 2, 29 | "key-spacing": [2, { "beforeColon": false, "afterColon": true }], 30 | "keyword-spacing": 2, 31 | "new-cap": 0, 32 | "no-native-reassign": 2, 33 | "no-extra-semi": 2, 34 | "no-multiple-empty-lines": [2, { "max": 1 }], 35 | // "no-param-reassign": [2, { "props": false }], 36 | "no-trailing-spaces": 2, 37 | "no-underscore-dangle": 0, 38 | "no-unused-vars": [ 39 | 2, 40 | { 41 | vars: "all", 42 | args: "none", 43 | caughtErrors: "all", 44 | argsIgnorePattern: "^_", 45 | caughtErrorsIgnorePattern: "^_" 46 | } 47 | ], 48 | "object-curly-spacing": [2, "always"], 49 | "padded-blocks": [2, "never"], 50 | "semi": [2, "always"], 51 | "space-before-blocks": [2, "always"], 52 | "no-var": 2, 53 | "prefer-const": [ 54 | 2, 55 | { 56 | destructuring: "any", 57 | ignoreReadBeforeAssign: true 58 | } 59 | ], 60 | 61 | "prefer-promise-reject-errors": 2, 62 | "prefer-template": 2 63 | } 64 | } 65 | ]; 66 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "axios-mock-adapter", 3 | "version": "2.1.0", 4 | "description": "Axios adapter that allows to easily mock requests", 5 | "main": "src/index.js", 6 | "scripts": { 7 | "clean": "node -e 'require(`fs`).rmSync(`./dist`, {recursive: true, force: true})'", 8 | "test": "mocha && npm run clean && npm run build:umd:min && npm run test:types", 9 | "test:coverage": "nyc --reporter=html --reporter=text-summary mocha", 10 | "test:types": "tsc --project types", 11 | "lint": "eslint src test", 12 | "build:umd": "webpack --mode development ./src/index.js --output-path ./dist", 13 | "build:umd:min": "webpack --mode production ./src/index.js --output-path ./dist", 14 | "prepublish": "npm run clean && npm run build:umd && npm run build:umd:min" 15 | }, 16 | "files": [ 17 | "src", 18 | "dist", 19 | "types" 20 | ], 21 | "types": "types", 22 | "repository": { 23 | "type": "git", 24 | "url": "git+https://github.com/ctimmerm/axios-mock-adapter.git" 25 | }, 26 | "keywords": [ 27 | "axios", 28 | "test", 29 | "mock", 30 | "request", 31 | "stub", 32 | "adapter" 33 | ], 34 | "author": "Colin Timmermans ", 35 | "license": "MIT", 36 | "bugs": { 37 | "url": "https://github.com/ctimmerm/axios-mock-adapter/issues" 38 | }, 39 | "homepage": "https://github.com/ctimmerm/axios-mock-adapter#readme", 40 | "peerDependencies": { 41 | "axios": ">= 0.17.0" 42 | }, 43 | "devDependencies": { 44 | "axios": "^1.7.4", 45 | "chai": "^4.3.6", 46 | "eslint": "^9.8.0", 47 | "mocha": "^10.7.0", 48 | "nyc": "^17.0.0", 49 | "typescript": "^5.5.4", 50 | "webpack": "^5.93.0", 51 | "webpack-cli": "^5.1.4" 52 | }, 53 | "dependencies": { 54 | "fast-deep-equal": "^3.1.3", 55 | "is-buffer": "^2.0.5" 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/handle_request.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const utils = require("./utils"); 3 | 4 | function passThroughRequest (mockAdapter, config) { 5 | // Axios v0.17 mutates the url to include the baseURL for non hostnames 6 | // but does not remove the baseURL from the config 7 | let baseURL = config.baseURL; 8 | if (baseURL && !/^https?:/.test(baseURL)) { 9 | baseURL = undefined; 10 | } 11 | 12 | // Axios pre 1.2 13 | if (typeof mockAdapter.originalAdapter === "function") { 14 | return mockAdapter.originalAdapter(config); 15 | } 16 | 17 | return mockAdapter.axiosInstanceWithoutInterceptors(Object.assign({}, config, { 18 | baseURL, 19 | // Use the original adapter, not the mock adapter 20 | adapter: mockAdapter.originalAdapter, 21 | // The request transformation runs on the original axios handler already 22 | transformRequest: [], 23 | transformResponse: [] 24 | })); 25 | } 26 | 27 | async function handleRequest(mockAdapter, config) { 28 | let url = config.url || ""; 29 | // TODO we're not hitting this `if` in any of the tests, investigate 30 | if ( 31 | config.baseURL && 32 | url.substr(0, config.baseURL.length) === config.baseURL 33 | ) { 34 | url = url.slice(config.baseURL.length); 35 | } 36 | 37 | delete config.adapter; 38 | mockAdapter.history.push(config); 39 | 40 | const handler = utils.findHandler( 41 | mockAdapter.handlers, 42 | config.method, 43 | url, 44 | config.data, 45 | config.params, 46 | (config.headers && config.headers.constructor.name === "AxiosHeaders") 47 | ? Object.assign({}, config.headers.toJSON()) 48 | : config.headers, 49 | config.baseURL 50 | ); 51 | 52 | if (handler) { 53 | if (handler.replyOnce) { 54 | utils.purgeIfReplyOnce(mockAdapter, handler); 55 | } 56 | 57 | if (handler.passThrough) { 58 | // passThrough handler 59 | return passThroughRequest(mockAdapter, config); 60 | } else { 61 | return utils.settle( 62 | config, 63 | handler.response, 64 | getEffectiveDelay(mockAdapter, handler) 65 | ); 66 | } 67 | } else { 68 | // handler not found 69 | switch (mockAdapter.onNoMatch) { 70 | case "passthrough": 71 | return passThroughRequest(mockAdapter, config); 72 | case "throwException": 73 | throw utils.createCouldNotFindMockError(config); 74 | default: 75 | return utils.settle( 76 | config, 77 | { status: 404 }, 78 | mockAdapter.delayResponse 79 | ); 80 | } 81 | } 82 | } 83 | 84 | function getEffectiveDelay(adapter, handler) { 85 | return typeof handler.delay === "number" ? handler.delay : adapter.delayResponse; 86 | } 87 | 88 | module.exports = handleRequest; 89 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const handleRequest = require("./handle_request"); 3 | const utils = require("./utils"); 4 | 5 | const VERBS = [ 6 | "get", 7 | "post", 8 | "head", 9 | "delete", 10 | "patch", 11 | "put", 12 | "options", 13 | "list", 14 | "link", 15 | "unlink", 16 | ]; 17 | 18 | function getVerbArray() { 19 | const arr = []; 20 | VERBS.forEach(function (verb) { 21 | Object.defineProperty(arr, verb, { 22 | get () { 23 | return arr.filter(function (h) { 24 | return !h.method || h.method === verb; 25 | }); 26 | }, 27 | }); 28 | }); 29 | return arr; 30 | } 31 | 32 | class AxiosMockAdapter { 33 | constructor (axiosInstance, options = {}) { 34 | this.reset(); 35 | 36 | if (axiosInstance) { 37 | this.axiosInstance = axiosInstance; 38 | // Clone the axios instance to remove interceptors 39 | // this is used for the passThrough mode with axios > 1.2 40 | this.axiosInstanceWithoutInterceptors = axiosInstance.create 41 | ? axiosInstance.create() 42 | : undefined; 43 | 44 | this.originalAdapter = axiosInstance.defaults.adapter; 45 | this.delayResponse = options.delayResponse > 0 ? options.delayResponse : null; 46 | this.onNoMatch = options.onNoMatch || null; 47 | axiosInstance.defaults.adapter = this.adapter(); 48 | } else { 49 | throw new Error("Please provide an instance of axios to mock"); 50 | } 51 | } 52 | 53 | adapter () { 54 | return (config) => handleRequest(this, config); 55 | } 56 | 57 | restore () { 58 | if (!this.axiosInstance) return; 59 | this.axiosInstance.defaults.adapter = this.originalAdapter; 60 | this.axiosInstance = undefined; 61 | } 62 | 63 | reset () { 64 | this.resetHandlers(); 65 | this.resetHistory(); 66 | } 67 | 68 | resetHandlers () { 69 | if (this.handlers) this.handlers.length = 0; 70 | else this.handlers = getVerbArray(); 71 | } 72 | 73 | resetHistory () { 74 | if (this.history) this.history.length = 0; 75 | else this.history = getVerbArray(); 76 | } 77 | } 78 | 79 | const methodsWithConfigsAsSecondArg = ["any", "get", "delete", "head", "options"]; 80 | function convertDataAndConfigToConfig (method, data, config) { 81 | if (methodsWithConfigsAsSecondArg.includes(method)) { 82 | return validateconfig(method, data || {}); 83 | } else { 84 | return validateconfig(method, Object.assign({}, config, { data: data })); 85 | } 86 | } 87 | 88 | const allowedConfigProperties = ["headers", "params", "data"]; 89 | function validateconfig (method, config) { 90 | for (const key in config) { 91 | if (!allowedConfigProperties.includes(key)) { 92 | throw new Error( 93 | `Invalid config property ${ 94 | JSON.stringify(key) 95 | } provided to ${ 96 | toMethodName(method) 97 | }. Config: ${ 98 | JSON.stringify(config)}` 99 | ); 100 | } 101 | } 102 | 103 | return config; 104 | } 105 | 106 | function toMethodName (method) { 107 | return `on${method.charAt(0).toUpperCase()}${method.slice(1)}`; 108 | } 109 | 110 | VERBS.concat("any").forEach(function (method) { 111 | AxiosMockAdapter.prototype[toMethodName(method)] = function (matcher, data, config) { 112 | const self = this; 113 | let delay; 114 | matcher = matcher === undefined ? /.*/ : matcher; 115 | 116 | const paramsAndBody = convertDataAndConfigToConfig(method, data, config); 117 | 118 | function reply (code, response, headers) { 119 | const handler = { 120 | url: matcher, 121 | method: method === "any" ? undefined : method, 122 | params: paramsAndBody.params, 123 | data: paramsAndBody.data, 124 | headers: paramsAndBody.headers, 125 | replyOnce: false, 126 | delay, 127 | response: typeof code === "function" ? code : [ 128 | code, 129 | response, 130 | headers 131 | ] 132 | }; 133 | addHandler(method, self.handlers, handler); 134 | return self; 135 | } 136 | 137 | function withDelayInMs (_delay) { 138 | delay = _delay; 139 | const respond = requestApi.reply.bind(requestApi); 140 | Object.assign(respond, requestApi); 141 | return respond; 142 | } 143 | 144 | function replyOnce (code, response, headers) { 145 | const handler = { 146 | url: matcher, 147 | method: method === "any" ? undefined : method, 148 | params: paramsAndBody.params, 149 | data: paramsAndBody.data, 150 | headers: paramsAndBody.headers, 151 | replyOnce: true, 152 | delay: delay, 153 | response: typeof code === "function" ? code : [ 154 | code, 155 | response, 156 | headers 157 | ] 158 | }; 159 | addHandler(method, self.handlers, handler); 160 | return self; 161 | } 162 | 163 | const requestApi = { 164 | reply, 165 | replyOnce, 166 | withDelayInMs, 167 | passThrough () { 168 | const handler = { 169 | passThrough: true, 170 | method: method === "any" ? undefined : method, 171 | url: matcher, 172 | params: paramsAndBody.params, 173 | data: paramsAndBody.data, 174 | headers: paramsAndBody.headers 175 | }; 176 | addHandler(method, self.handlers, handler); 177 | return self; 178 | }, 179 | abortRequest () { 180 | return reply(async function (config) { 181 | throw utils.createAxiosError( 182 | "Request aborted", 183 | config, 184 | undefined, 185 | "ECONNABORTED" 186 | ); 187 | }); 188 | }, 189 | abortRequestOnce () { 190 | return replyOnce(async function (config) { 191 | throw utils.createAxiosError( 192 | "Request aborted", 193 | config, 194 | undefined, 195 | "ECONNABORTED" 196 | ); 197 | }); 198 | }, 199 | 200 | networkError () { 201 | return reply(async function (config) { 202 | throw utils.createAxiosError("Network Error", config); 203 | }); 204 | }, 205 | 206 | networkErrorOnce () { 207 | return replyOnce(async function (config) { 208 | throw utils.createAxiosError("Network Error", config); 209 | }); 210 | }, 211 | 212 | timeout () { 213 | return reply(async function (config) { 214 | throw utils.createAxiosError( 215 | config.timeoutErrorMessage || 216 | `timeout of ${config.timeout }ms exceeded`, 217 | config, 218 | undefined, 219 | config.transitional && config.transitional.clarifyTimeoutError 220 | ? "ETIMEDOUT" 221 | : "ECONNABORTED" 222 | ); 223 | }); 224 | }, 225 | 226 | timeoutOnce () { 227 | return replyOnce(async function (config) { 228 | throw utils.createAxiosError( 229 | config.timeoutErrorMessage || 230 | `timeout of ${config.timeout }ms exceeded`, 231 | config, 232 | undefined, 233 | config.transitional && config.transitional.clarifyTimeoutError 234 | ? "ETIMEDOUT" 235 | : "ECONNABORTED" 236 | ); 237 | }); 238 | }, 239 | }; 240 | 241 | return requestApi; 242 | }; 243 | }); 244 | 245 | function findInHandlers (handlers, handler) { 246 | let index = -1; 247 | for (let i = 0; i < handlers.length; i += 1) { 248 | const item = handlers[i]; 249 | const comparePaths = 250 | item.url instanceof RegExp && handler.url instanceof RegExp 251 | ? String(item.url) === String(handler.url) 252 | : item.url === handler.url; 253 | 254 | const isSame = 255 | (!item.method || item.method === handler.method) && 256 | comparePaths && 257 | utils.isEqual(item.params, handler.params) && 258 | utils.isEqual(item.data, handler.data) && 259 | utils.isEqual(item.headers, handler.headers); 260 | 261 | if (isSame && !item.replyOnce) { 262 | index = i; 263 | } 264 | } 265 | return index; 266 | } 267 | 268 | function addHandler (method, handlers, handler) { 269 | if (method === "any") { 270 | handlers.push(handler); 271 | } else { 272 | const indexOfExistingHandler = findInHandlers(handlers, handler); 273 | // handler.replyOnce indicates that a handler only runs once. 274 | // It's supported to register muliple ones like that without 275 | // overwriting the previous one. 276 | if (indexOfExistingHandler > -1 && !handler.replyOnce) { 277 | handlers.splice(indexOfExistingHandler, 1, handler); 278 | } else { 279 | handlers.push(handler); 280 | } 281 | } 282 | } 283 | 284 | module.exports = AxiosMockAdapter; 285 | module.exports.default = AxiosMockAdapter; 286 | -------------------------------------------------------------------------------- /src/is_blob.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * MIT License 3 | * 4 | * Copyright (c) Sindre Sorhus (https://sindresorhus.com) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated 7 | * documentation files (the "Software"), to deal in the Software without restriction, including without limitation the 8 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit 9 | * persons to whom the Software is furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the 12 | * Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE 15 | * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 17 | * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 18 | */ 19 | 20 | function isBlob(value) { 21 | if (typeof Blob === "undefined") { 22 | return false; 23 | } 24 | 25 | return value instanceof Blob || Object.prototype.toString.call(value) === "[object Blob]"; 26 | } 27 | 28 | module.exports = isBlob; 29 | -------------------------------------------------------------------------------- /src/utils.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const axios = require("axios"); 3 | const isEqual = require("fast-deep-equal"); 4 | const isBuffer = require("is-buffer"); 5 | const isBlob = require("./is_blob"); 6 | const toString = Object.prototype.toString; 7 | 8 | function find(array, predicate) { 9 | const length = array.length; 10 | for (let i = 0; i < length; i++) { 11 | const value = array[i]; 12 | if (predicate(value)) return value; 13 | } 14 | } 15 | 16 | function isFunction(val) { 17 | return toString.call(val) === "[object Function]"; 18 | } 19 | 20 | function isObjectOrArray(val) { 21 | return val !== null && typeof val === "object"; 22 | } 23 | 24 | function isStream(val) { 25 | return isObjectOrArray(val) && isFunction(val.pipe); 26 | } 27 | 28 | function isArrayBuffer(val) { 29 | return toString.call(val) === "[object ArrayBuffer]"; 30 | } 31 | 32 | function combineUrls(baseURL, url) { 33 | if (baseURL) { 34 | return `${baseURL.replace(/\/+$/, "")}/${url.replace(/^\/+/, "")}`; 35 | } 36 | 37 | return url; 38 | } 39 | 40 | function findHandler( 41 | handlers, 42 | method, 43 | url, 44 | body, 45 | parameters, 46 | headers, 47 | baseURL 48 | ) { 49 | return find(handlers[method.toLowerCase()], function (handler) { 50 | let matchesUrl = false; 51 | if (typeof handler.url === "string") { 52 | matchesUrl = isUrlMatching(url, handler.url) || 53 | isUrlMatching(combineUrls(baseURL, url), handler.url); 54 | } else if (handler.url instanceof RegExp) { 55 | matchesUrl = handler.url.test(url) || 56 | handler.url.test(combineUrls(baseURL, url)); 57 | } 58 | 59 | return matchesUrl && 60 | isBodyOrParametersMatching(body, parameters, handler) && 61 | isObjectMatching(headers, handler.headers); 62 | }); 63 | } 64 | 65 | function isUrlMatching(url, required) { 66 | const noSlashUrl = url[0] === "/" ? url.substr(1) : url; 67 | const noSlashRequired = required[0] === "/" ? required.substr(1) : required; 68 | return noSlashUrl === noSlashRequired; 69 | } 70 | 71 | function isBodyOrParametersMatching(body, parameters, required) { 72 | return isObjectMatching(parameters, required.params) && 73 | isBodyMatching(body, required.data); 74 | } 75 | 76 | function isObjectMatching(actual, expected) { 77 | if (expected === undefined) return true; 78 | if (typeof expected.asymmetricMatch === "function") { 79 | return expected.asymmetricMatch(actual); 80 | } 81 | return isEqual(actual, expected); 82 | } 83 | 84 | function isBodyMatching(body, requiredBody) { 85 | if (requiredBody === undefined) { 86 | return true; 87 | } 88 | let parsedBody; 89 | try { 90 | parsedBody = JSON.parse(body); 91 | } catch (_e) {} 92 | 93 | return isObjectMatching(parsedBody ? parsedBody : body, requiredBody); 94 | } 95 | 96 | function purgeIfReplyOnce(mock, handler) { 97 | const index = mock.handlers.indexOf(handler); 98 | if (index > -1) { 99 | mock.handlers.splice(index, 1); 100 | } 101 | } 102 | 103 | function transformRequest(data) { 104 | if ( 105 | isArrayBuffer(data) || 106 | isBuffer(data) || 107 | isStream(data) || 108 | isBlob(data) 109 | ) { 110 | return data; 111 | } 112 | 113 | // Object and Array: returns a deep copy 114 | if (isObjectOrArray(data)) { 115 | return JSON.parse(JSON.stringify(data)); 116 | } 117 | 118 | // for primitives like string, undefined, null, number 119 | return data; 120 | } 121 | 122 | async function makeResponse(result, config) { 123 | if (typeof result === "function") result = await result(config); 124 | 125 | const status = result.status || result[0]; 126 | const data = transformRequest(result.data || result[1]); 127 | const headers = result.headers || result[2]; 128 | if (result.config) config = result.config; 129 | 130 | return { 131 | status, 132 | data, 133 | headers, 134 | config, 135 | request: { responseURL: config.url } 136 | }; 137 | } 138 | 139 | async function settle(config, response, delay) { 140 | if (delay > 0) await new Promise(resolve => setTimeout(resolve, delay)); 141 | 142 | const result = await makeResponse(response, config); 143 | 144 | if ( 145 | !result.config.validateStatus || 146 | result.config.validateStatus(result.status) 147 | ) { 148 | return result; 149 | } else { 150 | throw createAxiosError( 151 | `Request failed with status code ${result.status}`, 152 | result.config, 153 | result 154 | ); 155 | } 156 | } 157 | 158 | function createAxiosError(message, config, response, code) { 159 | // axios v0.27.0+ defines AxiosError as constructor 160 | if (typeof axios.AxiosError === "function") { 161 | return axios.AxiosError.from(new Error(message), code, config, null, response); 162 | } 163 | 164 | // handling for axios v0.26.1 and below 165 | const error = new Error(message); 166 | error.isAxiosError = true; 167 | error.config = config; 168 | if (response !== undefined) { 169 | error.response = response; 170 | } 171 | if (code !== undefined) { 172 | error.code = code; 173 | } 174 | 175 | error.toJSON = function toJSON() { 176 | return { 177 | // Standard 178 | message: this.message, 179 | name: this.name, 180 | // Microsoft 181 | description: this.description, 182 | number: this.number, 183 | // Mozilla 184 | fileName: this.fileName, 185 | lineNumber: this.lineNumber, 186 | columnNumber: this.columnNumber, 187 | stack: this.stack, 188 | // Axios 189 | config: this.config, 190 | code: this.code, 191 | }; 192 | }; 193 | return error; 194 | } 195 | 196 | function createCouldNotFindMockError(config) { 197 | const message = 198 | `Could not find mock for: \n${ 199 | JSON.stringify({ 200 | method: config.method, 201 | url: config.url, 202 | params: config.params, 203 | headers: config.headers 204 | }, null, 2)}`; 205 | const error = new Error(message); 206 | error.isCouldNotFindMockError = true; 207 | error.url = config.url; 208 | error.method = config.method; 209 | return error; 210 | } 211 | 212 | module.exports = { 213 | find, 214 | findHandler, 215 | purgeIfReplyOnce, 216 | settle, 217 | isObjectOrArray, 218 | isBuffer, 219 | isBlob, 220 | isBodyOrParametersMatching, 221 | isEqual, 222 | createAxiosError, 223 | createCouldNotFindMockError, 224 | }; 225 | -------------------------------------------------------------------------------- /test/abort_request.spec.js: -------------------------------------------------------------------------------- 1 | const axios = require("axios"); 2 | const expect = require("chai").expect; 3 | 4 | const MockAdapter = require("../src"); 5 | 6 | describe("requestAborted spec", function () { 7 | let instance; 8 | let mock; 9 | 10 | beforeEach(function () { 11 | instance = axios.create(); 12 | mock = new MockAdapter(instance); 13 | }); 14 | 15 | afterEach(function () { 16 | mock.restore(); 17 | }); 18 | 19 | it("mocks requestAborted response", function () { 20 | mock.onGet("/foo").abortRequest(); 21 | 22 | return instance.get("/foo").then( 23 | function () { 24 | expect.fail("should not be called"); 25 | }, 26 | function (error) { 27 | expect(error.config).to.exist; 28 | expect(error.code).to.equal("ECONNABORTED"); 29 | expect(error.message).to.equal("Request aborted"); 30 | expect(error.isAxiosError).to.be.true; 31 | } 32 | ); 33 | }); 34 | 35 | it("can abort a request only once", function () { 36 | mock.onGet("/foo").abortRequestOnce().onGet("/foo").reply(200); 37 | 38 | return instance 39 | .get("/foo") 40 | .then( 41 | function () {}, 42 | function () { 43 | return instance.get("/foo"); 44 | } 45 | ) 46 | .then(function (response) { 47 | expect(response.status).to.equal(200); 48 | }); 49 | }); 50 | }); 51 | -------------------------------------------------------------------------------- /test/asymmetric.spec.js: -------------------------------------------------------------------------------- 1 | const axios = require("axios"); 2 | const expect = require("chai").expect; 3 | const MockAdapter = require("../src"); 4 | 5 | describe("MockAdapter asymmetric matchers", function () { 6 | let instance; 7 | let mock; 8 | 9 | beforeEach(function () { 10 | instance = axios.create(); 11 | mock = new MockAdapter(instance); 12 | }); 13 | 14 | it("mocks a post request with a body matching the matcher", function () { 15 | mock 16 | .onPost("/anyWithBody", { 17 | asymmetricMatch: function (actual) { 18 | return actual.params === "1"; 19 | }, 20 | }) 21 | .reply(200); 22 | 23 | return instance 24 | .post("/anyWithBody", { params: "1" }) 25 | .then(function (response) { 26 | expect(response.status).to.equal(200); 27 | }); 28 | }); 29 | 30 | it("mocks a post request with a body not matching the matcher", function () { 31 | mock 32 | .onPost("/anyWithBody", { 33 | asymmetricMatch: function (actual) { 34 | return actual.params === "1"; 35 | }, 36 | }) 37 | .reply(200); 38 | 39 | return instance 40 | .post("/anyWithBody", { params: "2" }) 41 | .catch(function (error) { 42 | expect(error.message).to.eq("Request failed with status code 404"); 43 | }); 44 | }); 45 | }); 46 | -------------------------------------------------------------------------------- /test/basics.spec.js: -------------------------------------------------------------------------------- 1 | const axios = require("axios"); 2 | const fs = require("fs"); 3 | const expect = require("chai").expect; 4 | 5 | const MockAdapter = require("../src"); 6 | 7 | describe("MockAdapter basics", function () { 8 | let instance; 9 | let mock; 10 | 11 | beforeEach(function () { 12 | instance = axios.create(); 13 | mock = new MockAdapter(instance); 14 | }); 15 | 16 | it("correctly sets the adapter on the axios instance", function () { 17 | expect(instance.defaults.adapter).to.exist; 18 | }); 19 | 20 | it("correctly throws an error when attempting to instantiate an undefined axios instance", function () { 21 | const emptyInstance = undefined; 22 | const constructorFunc = function () { 23 | new MockAdapter(emptyInstance); 24 | }; 25 | expect(constructorFunc).to.throw( 26 | "Please provide an instance of axios to mock" 27 | ); 28 | }); 29 | 30 | it("calls interceptors", function () { 31 | instance.interceptors.response.use( 32 | function (config) { 33 | return config.data; 34 | }, 35 | function (error) { 36 | return Promise.reject(error); 37 | } 38 | ); 39 | 40 | mock.onGet("/foo").reply(200, { 41 | foo: "bar", 42 | }); 43 | 44 | return instance.get("/foo").then(function (response) { 45 | expect(response.foo).to.equal("bar"); 46 | }); 47 | }); 48 | 49 | it("supports all verbs", function () { 50 | expect(mock.onGet).to.be.a("function"); 51 | expect(mock.onPost).to.be.a("function"); 52 | expect(mock.onPut).to.be.a("function"); 53 | expect(mock.onHead).to.be.a("function"); 54 | expect(mock.onDelete).to.be.a("function"); 55 | expect(mock.onPatch).to.be.a("function"); 56 | expect(mock.onOptions).to.be.a("function"); 57 | expect(mock.onList).to.be.a("function"); 58 | expect(mock.onLink).to.be.a("function"); 59 | expect(mock.onUnlink).to.be.a("function"); 60 | }); 61 | 62 | it("mocks requests", function () { 63 | mock.onGet("/foo").reply(200, { 64 | foo: "bar", 65 | }); 66 | 67 | return instance.get("/foo").then(function (response) { 68 | expect(response.status).to.equal(200); 69 | expect(response.data.foo).to.equal("bar"); 70 | }); 71 | }); 72 | 73 | it("exposes the adapter", function () { 74 | expect(mock.adapter()).to.be.a("function"); 75 | 76 | instance.defaults.adapter = mock.adapter(); 77 | 78 | mock.onGet("/foo").reply(200, { 79 | foo: "bar", 80 | }); 81 | 82 | return instance.get("/foo").then(function (response) { 83 | expect(response.status).to.equal(200); 84 | expect(response.data.foo).to.equal("bar"); 85 | }); 86 | }); 87 | 88 | it("can return headers", function () { 89 | mock.onGet("/foo").reply( 90 | 200, 91 | {}, 92 | { 93 | foo: "bar", 94 | } 95 | ); 96 | 97 | return instance.get("/foo").then(function (response) { 98 | expect(response.status).to.equal(200); 99 | expect(response.headers.foo).to.equal("bar"); 100 | }); 101 | }); 102 | 103 | it("accepts a callback that returns a response", function () { 104 | mock.onGet("/foo").reply(function () { 105 | return [200, { foo: "bar" }]; 106 | }); 107 | 108 | return instance.get("/foo").then(function (response) { 109 | expect(response.status).to.equal(200); 110 | expect(response.data.foo).to.equal("bar"); 111 | }); 112 | }); 113 | 114 | it("accepts a callback that returns an axios request", function () { 115 | mock 116 | .onGet("/bar") 117 | .reply(200, { foo: "bar" }) 118 | .onGet("/foo") 119 | .reply(function () { 120 | return instance.get("/bar"); 121 | }); 122 | 123 | return instance.get("/foo").then(function (response) { 124 | expect(response.status).to.equal(200); 125 | expect(response.config.url).to.equal("/bar"); 126 | expect(response.data.foo).to.equal("bar"); 127 | }); 128 | }); 129 | 130 | it("matches on a regex", function () { 131 | mock.onGet(/\/fo+/).reply(200); 132 | 133 | return instance.get("/foooooooooo").then(function (response) { 134 | expect(response.status).to.equal(200); 135 | }); 136 | }); 137 | 138 | it("can pass query params for get to match to a handler", function () { 139 | mock 140 | .onGet("/withParams", { params: { foo: "bar", bar: "foo" } }) 141 | .reply(200); 142 | 143 | return instance 144 | .get("/withParams", { params: { bar: "foo", foo: "bar" } }) 145 | .then(function (response) { 146 | expect(response.status).to.equal(200); 147 | }); 148 | }); 149 | 150 | it("can pass query params for delete to match to a handler", function () { 151 | mock 152 | .onDelete("/withParams", { params: { foo: "bar", bar: "foo" } }) 153 | .reply(200); 154 | 155 | return instance 156 | .delete("/withParams", { params: { bar: "foo", foo: "bar" } }) 157 | .then(function (response) { 158 | expect(response.status).to.equal(200); 159 | }); 160 | }); 161 | 162 | it("can pass a body for delete to match to a handler", function () { 163 | mock 164 | .onDelete("/withParams",{ data: { bar: 2 }, params: { foo: 1 } }) 165 | .reply(200); 166 | 167 | return instance 168 | .delete("/withParams", { params: { foo: 1 }, data: { bar: 2 } } ) 169 | .then(function (response) { 170 | expect(response.status).to.equal(200); 171 | }); 172 | }); 173 | 174 | it("can pass query params for head to match to a handler", function () { 175 | mock 176 | .onHead("/withParams", { params: { foo: "bar", bar: "foo" } }) 177 | .reply(200); 178 | 179 | return instance 180 | .head("/withParams", { params: { bar: "foo", foo: "bar" } }) 181 | .then(function (response) { 182 | expect(response.status).to.equal(200); 183 | }); 184 | }); 185 | 186 | it("can pass query params for post to match to a handler", function () { 187 | mock 188 | .onPost("/withParams", undefined, { params: { foo: "bar", bar: "foo" } }) 189 | .reply(200); 190 | 191 | return instance 192 | .post("/withParams", { some: "body" }, { params: { foo: "bar", bar: "foo" } }) 193 | .then(function (response) { 194 | expect(response.status).to.equal(200); 195 | }); 196 | }); 197 | 198 | it("can pass query params for put to match to a handler", function () { 199 | mock 200 | .onPut("/withParams", undefined, { params: { foo: "bar", bar: "foo" } }) 201 | .reply(200); 202 | 203 | return instance 204 | .put("/withParams", { some: "body" }, { params: { bar: "foo", foo: "bar" } }) 205 | .then(function (response) { 206 | expect(response.status).to.equal(200); 207 | }); 208 | }); 209 | 210 | it("can pass query params to match to a handler with uppercase method", function () { 211 | mock 212 | .onGet("/withParams", { params: { foo: "bar", bar: "foo" } }) 213 | .reply(200); 214 | 215 | return instance({ 216 | method: "GET", 217 | url: "/withParams", 218 | params: { foo: "bar", bar: "foo" }, 219 | }).then(function (response) { 220 | expect(response.status).to.equal(200); 221 | }); 222 | }); 223 | 224 | it("does not match when params are wrong", function () { 225 | mock 226 | .onGet("/withParams", { params: { foo: "bar", bar: "foo" } }) 227 | .reply(200); 228 | return instance 229 | .get("/withParams", { params: { foo: "bar", bar: "other" } }) 230 | .catch(function (error) { 231 | expect(error.response.status).to.equal(404); 232 | }); 233 | }); 234 | 235 | it("does not match when params are missing", function () { 236 | mock 237 | .onGet("/withParams", { params: { foo: "bar", bar: "foo" } }) 238 | .reply(200); 239 | return instance.get("/withParams").catch(function (error) { 240 | expect(error.response.status).to.equal(404); 241 | }); 242 | }); 243 | 244 | it("matches when params were not expected", function () { 245 | mock.onGet("/withParams").reply(200); 246 | return instance 247 | .get("/withParams", { params: { foo: "bar", bar: "foo" } }) 248 | .then(function (response) { 249 | expect(response.status).to.equal(200); 250 | }); 251 | }); 252 | 253 | it("can pass a body to match to a handler", function () { 254 | mock.onPost("/withBody", { somecontent: { is: "passed" } }).reply(200); 255 | 256 | return instance 257 | .post("/withBody", { somecontent: { is: "passed" } }) 258 | .then(function (response) { 259 | expect(response.status).to.equal(200); 260 | }); 261 | }); 262 | 263 | it("does not match when body is wrong", function () { 264 | const matcher = { somecontent: { is: "passed" } }; 265 | mock.onPatch("/wrongObjBody", matcher).reply(200); 266 | 267 | return instance 268 | .patch("/wrongObjBody", { wrong: "body" }) 269 | .catch(function (error) { 270 | expect(error.response.status).to.equal(404); 271 | }); 272 | }); 273 | 274 | it("does not match when string body is wrong", function () { 275 | mock.onPatch("/wrongStrBody", "foo").reply(200); 276 | 277 | return instance.patch("/wrongStrBody", "bar").catch(function (error) { 278 | expect(error.response.status).to.equal(404); 279 | }); 280 | }); 281 | 282 | it("does match with string body", function () { 283 | mock.onPatch(/^\/strBody$/, "foo").reply(200); 284 | 285 | return instance.patch("/strBody", "foo").then(function (response) { 286 | expect(response.status).to.equal(200); 287 | }); 288 | }); 289 | 290 | it("can pass headers to match to a handler", function () { 291 | const headers = { 292 | Accept: "application/json, text/plain, */*", 293 | "Content-Type": "application/x-www-form-urlencoded", 294 | "Header-test": "test-header", 295 | }; 296 | 297 | mock.onPost("/withHeaders", undefined, { headers: headers }).reply(200); 298 | 299 | return instance 300 | .post("/withHeaders", undefined, { headers: headers }) 301 | .then(function (response) { 302 | expect(response.status).to.equal(200); 303 | 304 | return instance 305 | .post("/withHeaders", undefined, { headers: { Accept: "no-match" } }) 306 | .catch(function (err) { 307 | expect(err.response.status).to.equal(404); 308 | }); 309 | }); 310 | }); 311 | 312 | it("does not match when request header is wrong", function () { 313 | const headers = { "Header-test": "test-header" }; 314 | mock.onPatch("/wrongObjHeader", undefined, { headers: headers }).reply(200); 315 | 316 | return instance 317 | .patch("/wrongObjHeader", undefined, { 318 | headers: { "Header-test": "wrong-header" }, 319 | }) 320 | .catch(function (error) { 321 | expect(error.response.status).to.equal(404); 322 | }); 323 | }); 324 | 325 | it("passes the config to the callback", function () { 326 | mock.onGet(/\/products\/\d+/).reply(function (config) { 327 | return [200, {}, { RequestedURL: config.url }]; 328 | }); 329 | 330 | return instance.get("/products/25").then(function (response) { 331 | expect(response.headers.RequestedURL).to.equal("/products/25"); 332 | }); 333 | }); 334 | 335 | it("handles post requests", function () { 336 | mock.onPost("/foo").reply(function (config) { 337 | return [200, JSON.parse(config.data).bar]; 338 | }); 339 | 340 | return instance.post("/foo", { bar: "baz" }).then(function (response) { 341 | expect(response.data).to.equal("baz"); 342 | }); 343 | }); 344 | 345 | it("works when using baseURL", function () { 346 | instance.defaults.baseURL = "http://www.example.org"; 347 | 348 | mock.onGet("/foo").reply(200); 349 | 350 | return instance.get("/foo").then(function (response) { 351 | expect(response.status).to.equal(200); 352 | }); 353 | }); 354 | 355 | it("allows using an absolute URL when a baseURL is set", function () { 356 | instance.defaults.baseURL = "http://www.example.org"; 357 | 358 | mock.onAny().reply(function (config) { 359 | return [200, config.url]; 360 | }); 361 | 362 | return instance.get("http://www.foo.com/bar").then(function (response) { 363 | expect(response.status).to.equal(200); 364 | expect(response.data).to.equal("http://www.foo.com/bar"); 365 | }); 366 | }); 367 | 368 | // https://github.com/ctimmerm/axios-mock-adapter/issues/74 369 | it("allows mocks to match on the result of concatenating baseURL and url", function () { 370 | instance.defaults.baseURL = "http://www.example.org/api/v1/"; 371 | 372 | mock.onGet("http://www.example.org/api/v1/foo").reply(200); 373 | 374 | return instance.get("/foo").then(function (response) { 375 | expect(response.status).to.equal(200); 376 | }); 377 | }); 378 | 379 | // https://github.com/ctimmerm/axios-mock-adapter/issues/74 380 | it("allows mocks to match on the result of concatenating baseURL and url with a regex", function () { 381 | instance.defaults.baseURL = "http://www.example.org/api/v1/"; 382 | 383 | mock.onGet(/\/api\/v1\/foo$/).reply(200); 384 | 385 | return instance.get("/foo").then(function (response) { 386 | expect(response.status).to.equal(200); 387 | }); 388 | }); 389 | 390 | it("allows multiple consecutive requests for the mocked url", function () { 391 | mock.onGet("/foo").reply(200); 392 | 393 | return instance 394 | .get("/foo") 395 | .then(function () { 396 | return instance.get("/foo"); 397 | }) 398 | .then(function (response) { 399 | expect(response.status).to.equal(200); 400 | }); 401 | }); 402 | 403 | it("returns a 404 when no matching url is found", function () { 404 | return instance.get("/foo").catch(function (error) { 405 | expect(error.response.status).to.equal(404); 406 | }); 407 | }); 408 | 409 | it("rejects when the status is >= 300", function () { 410 | mock.onGet("/moo").reply(500); 411 | 412 | return instance.get("/moo").catch(function (error) { 413 | expect(error.response.status).to.equal(500); 414 | }); 415 | }); 416 | 417 | it("rejects the promise with an error when the status is >= 300", function () { 418 | mock.onGet("/foo").reply(500); 419 | 420 | return instance.get("/foo").catch(function (error) { 421 | expect(error).to.be.an.instanceof(Error); 422 | expect(error.message).to.match(/request failed/i); 423 | }); 424 | }); 425 | 426 | it("supports providing a validateStatus function", function () { 427 | instance.defaults.validateStatus = function () { 428 | return true; 429 | }; 430 | mock.onGet("/foo").reply(500); 431 | 432 | return instance.get("/foo").then(function (response) { 433 | expect(response.status).to.equal(500); 434 | }); 435 | }); 436 | 437 | it("supports providing a validateStatus null value", function () { 438 | instance.defaults.validateStatus = null; 439 | mock.onGet("/foo").reply(500); 440 | 441 | return instance.get("/foo").then(function (response) { 442 | expect(response.status).to.equal(500); 443 | }); 444 | }); 445 | 446 | it("respects validatesStatus when requesting unhandled urls", function () { 447 | instance.defaults.validateStatus = function () { 448 | return true; 449 | }; 450 | 451 | return instance.get("/foo").then(function (response) { 452 | expect(response.status).to.equal(404); 453 | }); 454 | }); 455 | 456 | it("handles errors thrown as expected", function () { 457 | mock.onGet("/foo").reply(function () { 458 | throw new Error("bar"); 459 | }); 460 | 461 | return instance.get("/foo").catch(function (error) { 462 | expect(error).to.be.an.instanceof(Error); 463 | expect(error.message).to.equal("bar"); 464 | }); 465 | }); 466 | 467 | it("restores the previous adapter (if any)", function () { 468 | const adapter = function () {}; 469 | const newInstance = axios.create(); 470 | newInstance.defaults.adapter = adapter; 471 | const newMock = new MockAdapter(newInstance); 472 | newMock.restore(); 473 | 474 | expect(newInstance.defaults.adapter).to.equal(adapter); 475 | }); 476 | 477 | it("performs a noop when restore is called more than once", function () { 478 | mock.restore(); 479 | const newAdapter = function () {}; 480 | instance.defaults.adapter = newAdapter; 481 | mock.restore(); 482 | expect(instance.defaults.adapter).to.equal(newAdapter); 483 | }); 484 | 485 | it("resets the registered mock handlers", function () { 486 | mock.onGet("/foo").reply(200); 487 | expect(mock.handlers["get"]).not.to.be.empty; 488 | 489 | mock.reset(); 490 | expect(mock.handlers["get"]).to.be.empty; 491 | }); 492 | 493 | it("resets the history", function () { 494 | mock.onAny("/foo").reply(200); 495 | 496 | return instance.get("/foo").then(function (response) { 497 | mock.reset(); 498 | expect(mock.history["get"]).to.eql([]); 499 | }); 500 | }); 501 | 502 | it("resets only the registered mock handlers, not the history", function () { 503 | mock.onAny("/foo").reply(200); 504 | expect(mock.handlers["get"]).not.to.be.empty; 505 | expect(mock.history["get"]).to.eql([]); 506 | 507 | return instance.get("/foo").then(function (response) { 508 | mock.resetHandlers(); 509 | expect(mock.history.get.length).to.equal(1); 510 | expect(mock.handlers["get"]).to.be.empty; 511 | }); 512 | }); 513 | 514 | it("does not fail if reset is called after restore", function () { 515 | mock.restore(); 516 | expect(mock.reset()).to.not.throw; 517 | }); 518 | 519 | it("can chain calls to add mock handlers", function () { 520 | mock 521 | .onGet("/foo") 522 | .reply(200) 523 | .onAny("/bar") 524 | .reply(404) 525 | .onPost("/baz") 526 | .reply(500); 527 | 528 | expect(mock.handlers["get"].length).to.equal(2); 529 | expect(mock.handlers["patch"].length).to.equal(1); 530 | expect(mock.handlers["post"].length).to.equal(2); 531 | }); 532 | 533 | it("allows to delay responses", function () { 534 | mock = new MockAdapter(instance, { delayResponse: 1 }); 535 | 536 | mock.onGet("/foo").reply(200); 537 | 538 | return instance.get("/foo").then(function (response) { 539 | expect(response.status).to.equal(200); 540 | }); 541 | }); 542 | 543 | it("allows to delay error responses", function () { 544 | mock = new MockAdapter(instance, { delayResponse: 1 }); 545 | 546 | mock.onGet("/foo").reply(500); 547 | 548 | return instance.get("/foo").catch(function (error) { 549 | expect(error.response.status).to.equal(500); 550 | }); 551 | }); 552 | 553 | it("allows to delay responses when the response promise is rejected", function () { 554 | mock = new MockAdapter(instance, { delayResponse: 1 }); 555 | 556 | mock.onGet("/foo").reply(function (config) { 557 | return Promise.reject(new Error("error")); 558 | }); 559 | 560 | return instance.get("/foo").catch(function (err) { 561 | expect(err.message).to.equal("error"); 562 | }); 563 | }); 564 | 565 | it("allows delay in millsecond per request (legacy non-chaining)", function () { 566 | mock = new MockAdapter(instance); 567 | const start = performance.now(); 568 | const firstDelay = 100; 569 | const secondDelay = 500; 570 | const success = 200; 571 | 572 | const fooOnDelayResponds = mock.onGet("/foo").withDelayInMs(firstDelay); 573 | fooOnDelayResponds(success); 574 | const barOnDelayResponds = mock.onGet("/bar").withDelayInMs(secondDelay); 575 | barOnDelayResponds(success); 576 | 577 | return Promise.all([ 578 | instance.get("/foo").then(function (response) { 579 | const end = performance.now(); 580 | const totalTime = end - start; 581 | 582 | expect(response.status).to.equal(success); 583 | expect(totalTime).greaterThanOrEqual(firstDelay - 1); 584 | }), 585 | instance.get("/bar").then(function (response) { 586 | const end = performance.now(); 587 | const totalTime = end - start; 588 | 589 | expect(response.status).to.equal(success); 590 | expect(totalTime).greaterThanOrEqual(secondDelay - 1); 591 | }) 592 | ]); 593 | }); 594 | 595 | it("allows delay in millsecond per request", function () { 596 | mock = new MockAdapter(instance); 597 | const start = performance.now(); 598 | const firstDelay = 100; 599 | const secondDelay = 500; 600 | const success = 200; 601 | 602 | mock.onGet("/foo") 603 | .withDelayInMs(firstDelay) 604 | .reply(success); 605 | 606 | mock.onGet("/bar") 607 | .withDelayInMs(secondDelay) 608 | .reply(success); 609 | 610 | return Promise.all([ 611 | instance.get("/foo").then(function (response) { 612 | const end = performance.now(); 613 | const totalTime = end - start; 614 | 615 | expect(response.status).to.equal(success); 616 | expect(totalTime).greaterThanOrEqual(firstDelay - 1); 617 | }), 618 | instance.get("/bar").then(function (response) { 619 | const end = performance.now(); 620 | const totalTime = end - start; 621 | 622 | expect(response.status).to.equal(success); 623 | expect(totalTime).greaterThanOrEqual(secondDelay - 1); 624 | }) 625 | ]); 626 | }); 627 | 628 | it("overrides global delay if request per delay is provided and respects global delay if otherwise", function () { 629 | const start = performance.now(); 630 | const requestDelay = 100; 631 | const globalDelay = 500; 632 | const success = 200; 633 | mock = new MockAdapter(instance, { delayResponse: globalDelay }); 634 | 635 | const fooOnDelayResponds = mock.onGet("/foo").withDelayInMs(requestDelay); 636 | fooOnDelayResponds(success); 637 | mock.onGet("/bar").reply(success); 638 | 639 | return Promise.all([ 640 | instance.get("/foo").then(function (response) { 641 | const end = performance.now(); 642 | const totalTime = end - start; 643 | 644 | expect(response.status).to.equal(success); 645 | expect(totalTime).greaterThanOrEqual(requestDelay - 1); 646 | //Ensure global delay is not applied 647 | expect(totalTime).lessThan(globalDelay); 648 | }), 649 | instance.get("/bar").then(function (response) { 650 | const end = performance.now(); 651 | const totalTime = end - start; 652 | 653 | expect(response.status).to.equal(success); 654 | expect(totalTime).greaterThanOrEqual(globalDelay - 1); 655 | }) 656 | ]); 657 | }); 658 | 659 | it("maps empty GET path to any path", function () { 660 | mock.onGet("/foo").reply(200, "foo").onGet().reply(200, "bar"); 661 | 662 | return Promise.all([ 663 | instance.get("/foo").then(function (response) { 664 | expect(response.status).to.equal(200); 665 | expect(response.data).to.equal("foo"); 666 | }), 667 | instance.get("/bar").then(function (response) { 668 | expect(response.status).to.equal(200); 669 | expect(response.data).to.equal("bar"); 670 | }), 671 | instance 672 | .get(`/xyz${Math.round(100000 * Math.random())}`) 673 | .then(function (response) { 674 | expect(response.status).to.equal(200); 675 | expect(response.data).to.equal("bar"); 676 | }), 677 | ]); 678 | }); 679 | 680 | it("allows mocking all requests", function () { 681 | mock.onAny().reply(200); 682 | 683 | function anyResponseTester(response) { 684 | expect(response.status).to.equal(200); 685 | } 686 | 687 | return Promise.all([ 688 | instance.get("/foo").then(anyResponseTester), 689 | instance.post("/bar").then(anyResponseTester), 690 | instance.put("/foobar").then(anyResponseTester), 691 | instance.head("/barfoo").then(anyResponseTester), 692 | instance.delete("/foo/bar").then(anyResponseTester), 693 | instance.patch("/bar/foo").then(anyResponseTester), 694 | ]); 695 | }); 696 | 697 | it("returns a deep copy of the mock data in the response when the data is an object", function () { 698 | const data = { 699 | foo: { 700 | bar: 123, 701 | }, 702 | }; 703 | 704 | mock.onGet("/").reply(200, data); 705 | 706 | return instance 707 | .get("/") 708 | .then(function (response) { 709 | response.data.foo.bar = 456; 710 | }) 711 | .then(function () { 712 | expect(data.foo.bar).to.equal(123); 713 | }); 714 | }); 715 | 716 | it("returns a deep copy of the mock data in the response when the data is an array", function () { 717 | const data = [ 718 | { 719 | bar: 123, 720 | }, 721 | ]; 722 | 723 | mock.onGet("/").reply(200, data); 724 | 725 | return instance 726 | .get("/") 727 | .then(function (response) { 728 | response.data[0].bar = 456; 729 | }) 730 | .then(function () { 731 | expect(data[0].bar).to.equal(123); 732 | }); 733 | }); 734 | 735 | it("can overwrite an existing mock", function () { 736 | mock.onGet("/").reply(500); 737 | mock.onGet("/").reply(200); 738 | 739 | return instance.get("/").then(function (response) { 740 | expect(response.status).to.equal(200); 741 | }); 742 | }); 743 | 744 | it("does not add duplicate handlers", function () { 745 | mock.onGet("/").replyOnce(312); 746 | mock.onGet("/").reply(200); 747 | mock.onGet("/1").reply(200); 748 | mock.onGet("/2").reply(200); 749 | mock.onGet("/3").replyOnce(300); 750 | mock.onGet("/3").reply(200); 751 | mock.onGet("/4").reply(200); 752 | 753 | expect(mock.handlers["get"].length).to.equal(7); 754 | }); 755 | 756 | it("supports chaining on same path with different params", function () { 757 | mock 758 | .onGet("/users", { params: { searchText: "John" } }) 759 | .reply(200, { id: 1 }) 760 | .onGet("/users", { params: { searchText: "James" } }) 761 | .reply(200, { id: 2 }) 762 | .onGet("/users", { params: { searchText: "Jake" } }) 763 | .reply(200, { id: 3 }) 764 | .onGet("/users", { params: { searchText: "Jackie" } }) 765 | .reply(200, { id: 4 }); 766 | 767 | return instance 768 | .get("/users", { params: { searchText: "John" } }) 769 | .then(function (response) { 770 | expect(response.data.id).to.equal(1); 771 | return instance.get("/users", { params: { searchText: "James" } }); 772 | }) 773 | .then(function (response) { 774 | expect(response.data.id).to.equal(2); 775 | return instance.get("/users", { params: { searchText: "Jake" } }); 776 | }) 777 | .then(function (response) { 778 | expect(response.data.id).to.equal(3); 779 | return instance.get("/users", { params: { searchText: "Jackie" } }); 780 | }) 781 | .then(function (response) { 782 | expect(response.data.id).to.equal(4); 783 | }); 784 | }); 785 | 786 | it("can overwrite replies", function () { 787 | mock.onGet("/").reply(500); 788 | mock.onGet("/").reply(200); 789 | mock.onGet("/").reply(401); 790 | 791 | return instance.get("/").catch(function (error) { 792 | expect(mock.handlers["get"].length).to.equal(1); 793 | expect(error.response.status).to.equal(401); 794 | }); 795 | }); 796 | 797 | it("can overwrite replies using RegEx", function () { 798 | mock.onGet(/foo\/bar/).reply(500); 799 | mock.onGet(/foo\/bar/).reply(200); 800 | mock.onGet(/foo\/baz\/.+/).reply(200); 801 | 802 | return instance 803 | .get("/foo/bar") 804 | .then(function (response) { 805 | expect(mock.handlers["get"].length).to.equal(2); 806 | expect(response.status).to.equal(200); 807 | return instance.get("/foo/baz/56"); 808 | }) 809 | .then(function (response) { 810 | expect(response.status).to.equal(200); 811 | }); 812 | }); 813 | 814 | it("allows overwriting only on reply if replyOnce was used first", function () { 815 | let counter = 0; 816 | mock.onGet("/").replyOnce(500); 817 | mock.onGet("/").reply(200); 818 | mock.onGet("/").reply(401); 819 | 820 | return instance 821 | .get("/") 822 | .catch(function (error) { 823 | expect(error.response.status).to.equal(500); 824 | counter += 1; 825 | return instance.get("/"); 826 | }) 827 | .catch(function (error) { 828 | expect(error.response.status).to.equal(401); 829 | counter += 1; 830 | }) 831 | .then(function () { 832 | expect(counter).to.equal(2); 833 | }); 834 | }); 835 | 836 | it("should not allow overwriting only on reply if replyOnce wasn't used first", function () { 837 | let counter = 0; 838 | mock.onGet("/").reply(200); 839 | mock.onGet("/").reply(401); 840 | mock.onGet("/").replyOnce(500); 841 | mock.onGet("/").reply(500); 842 | 843 | return instance 844 | .get("/") 845 | .catch(function (error) { 846 | expect(error.response.status).to.equal(500); 847 | counter += 1; 848 | return instance.get("/"); 849 | }) 850 | .catch(function (error) { 851 | expect(error.response.status).to.equal(500); 852 | counter += 1; 853 | }) 854 | .then(function () { 855 | expect(counter).to.equal(2); 856 | }); 857 | }); 858 | 859 | it("allows overwriting mocks with params", function () { 860 | mock 861 | .onGet("/users", { params: { searchText: "John" } }) 862 | .reply(500) 863 | .onGet("/users", { params: { searchText: "John" } }) 864 | .reply(200, { id: 1 }); 865 | 866 | return instance 867 | .get("/users", { params: { searchText: "John" } }) 868 | .then(function (response) { 869 | expect(response.status).to.equal(200); 870 | }); 871 | }); 872 | 873 | it("allows overwriting mocks with headers", function () { 874 | mock.onGet("/", {}, { "Accept-Charset": "utf-8" }).reply(500); 875 | mock.onGet("/", {}, { "Accept-Charset": "utf-8" }).reply(200); 876 | 877 | expect(mock.handlers["get"].length).to.equal(1); 878 | expect(mock.handlers["get"][0].response[0]).to.equal(200); 879 | }); 880 | 881 | it("supports a retry", function () { 882 | mock.onGet("/").replyOnce(401); 883 | mock.onGet("/").replyOnce(201); 884 | instance.interceptors.response.use(undefined, function (error) { 885 | if (error.response && error.response.status === 401) { 886 | return instance(error.config); 887 | } 888 | return Promise.reject(error); 889 | }); 890 | return instance({ method: "get", url: "/" }).then(function (response) { 891 | expect(response.status).to.equal(201); 892 | }); 893 | }); 894 | 895 | it("allows sending a stream as response", function (done) { 896 | instance.defaults.baseURL = "http://www.example.org"; 897 | 898 | mock.onAny().reply(function (config) { 899 | return [200, fs.createReadStream(__filename)]; 900 | }); 901 | 902 | instance 903 | .get("http://www.foo.com/bar", { responseType: "stream" }) 904 | .then(function (response) { 905 | expect(response.status).to.equal(200); 906 | const stream = response.data; 907 | let string = ""; 908 | stream.on("data", function (chunk) { 909 | string += chunk.toString("utf8"); 910 | }); 911 | stream.on("end", function () { 912 | expect(string).to.equal(fs.readFileSync(__filename, "utf8")); 913 | done(); 914 | }); 915 | }); 916 | }); 917 | 918 | it("allows sending a buffer as response", function () { 919 | instance.defaults.baseURL = "http://www.example.org"; 920 | 921 | mock.onAny().reply(function (config) { 922 | return [200, Buffer.from("fooBar", "utf8")]; 923 | }); 924 | 925 | return instance 926 | .get("http://www.foo.com/bar", { responseType: "stream" }) 927 | .then(function (response) { 928 | expect(response.status).to.equal(200); 929 | const string = response.data.toString("utf8"); 930 | expect(string).to.equal("fooBar"); 931 | }); 932 | }); 933 | 934 | it("allows sending an array as response", function () { 935 | mock.onGet("/").reply(200, [1, 2, 3]); 936 | 937 | return instance.get("/").then(function (response) { 938 | expect(response.data).to.deep.equal([1, 2, 3]); 939 | }); 940 | }); 941 | 942 | it("allows sending an Uint8Array as response", function () { 943 | const buffer = new ArrayBuffer(1); 944 | const view = new Uint8Array(buffer); 945 | view[0] = 0xff; 946 | 947 | mock.onGet("/").reply(200, buffer); 948 | 949 | return instance({ 950 | url: "/", 951 | method: "GET", 952 | responseType: "arraybuffer", 953 | }).then(function (response) { 954 | const view = new Uint8Array(response.data); 955 | expect(view[0]).to.equal(0xff); 956 | }); 957 | }); 958 | 959 | it("returns the original request url in the response.request.responseURL property", function () { 960 | mock.onGet("/foo").reply(200, { 961 | foo: "bar", 962 | }); 963 | 964 | return instance.get("/foo").then(function (response) { 965 | expect(response.status).to.equal(200); 966 | expect(response.data.foo).to.equal("bar"); 967 | expect(response.request.responseURL).to.equal("/foo"); 968 | }); 969 | }); 970 | 971 | it("sets isAxiosError property on errors", function () { 972 | mock.onGet("/").reply(404); 973 | 974 | return instance 975 | .get("/") 976 | .then(function () { 977 | expect(true).to.be.false; 978 | }) 979 | .catch(function (error) { 980 | expect(error.isAxiosError).to.be.true; 981 | }); 982 | }); 983 | 984 | it("sets toJSON method on errors", function () { 985 | mock.onGet("/").reply(404); 986 | 987 | return instance 988 | .get("/") 989 | .then(function () { 990 | expect(true).to.be.false; 991 | }) 992 | .catch(function (error) { 993 | const serializableError = error.toJSON(); 994 | expect(serializableError.message).to.equal( 995 | "Request failed with status code 404" 996 | ); 997 | expect(serializableError.name).to.equal("Error"); 998 | expect(serializableError.stack).to.exist; 999 | expect(serializableError.config).to.exist; 1000 | }); 1001 | }); 1002 | }); 1003 | -------------------------------------------------------------------------------- /test/cancelation.spec.js: -------------------------------------------------------------------------------- 1 | const axios = require("axios"); 2 | const expect = require("chai").expect; 3 | 4 | const MockAdapter = require("../src"); 5 | const CancelToken = axios.CancelToken; 6 | 7 | describe("MockAdapter basics", function () { 8 | let instance; 9 | let mock; 10 | 11 | beforeEach(function () { 12 | instance = axios.create(); 13 | mock = new MockAdapter(instance); 14 | }); 15 | 16 | it("handles canceled requests", function () { 17 | const source = CancelToken.source(); 18 | 19 | mock.onGet("/foo").reply(200); 20 | 21 | source.cancel("Operation canceled"); 22 | 23 | return instance 24 | .get("/foo", { 25 | cancelToken: source.token, 26 | }) 27 | .then(function () { 28 | expect(true).to.be.false; 29 | }) 30 | .catch(function (error) { 31 | expect(axios.isCancel(error)).to.be.true; 32 | expect(error.message).to.equal("Operation canceled"); 33 | }); 34 | }); 35 | 36 | it("works as normal is request is not canceled", function () { 37 | const source = CancelToken.source(); 38 | 39 | mock.onGet("/foo").reply(200); 40 | 41 | return instance 42 | .get("/foo", { 43 | cancelToken: source.token, 44 | }) 45 | .then(function (response) { 46 | expect(response.status).to.equal(200); 47 | }); 48 | }); 49 | }); 50 | -------------------------------------------------------------------------------- /test/default_instance.spec.js: -------------------------------------------------------------------------------- 1 | const axios = require("axios"); 2 | const expect = require("chai").expect; 3 | 4 | const MockAdapter = require("../src"); 5 | 6 | describe("MockAdapter on default axios instance", function () { 7 | let mock; 8 | 9 | beforeEach(function () { 10 | mock = new MockAdapter(axios); 11 | }); 12 | 13 | afterEach(function () { 14 | mock.restore(); 15 | }); 16 | 17 | it("mocks requests on the default instance", function () { 18 | mock.onGet("/foo").reply(200); 19 | 20 | return axios.get("/foo").then(function (response) { 21 | expect(response.status).to.equal(200); 22 | }); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /test/history.spec.js: -------------------------------------------------------------------------------- 1 | const axios = require("axios"); 2 | const expect = require("chai").expect; 3 | 4 | const MockAdapter = require("../src"); 5 | 6 | describe("MockAdapter history", function () { 7 | let instance; 8 | let mock; 9 | 10 | beforeEach(function () { 11 | instance = axios.create(); 12 | mock = new MockAdapter(instance); 13 | }); 14 | 15 | it("initializes empty history for each http method", function () { 16 | expect(mock.history["get"]).to.eql([]); 17 | expect(mock.history["post"]).to.eql([]); 18 | expect(mock.history["put"]).to.eql([]); 19 | }); 20 | 21 | it("records the axios config each time the handler is invoked", function () { 22 | mock.onAny("/foo").reply(200); 23 | 24 | return instance.get("/foo").then(function (response) { 25 | expect(mock.history.get.length).to.equal(1); 26 | expect(mock.history.get[0].method).to.equal("get"); 27 | expect(mock.history.get[0].url).to.equal("/foo"); 28 | }); 29 | }); 30 | 31 | it("reset history should reset all history", function () { 32 | mock.onAny("/foo").reply(200); 33 | 34 | return instance.get("/foo").then(function (response) { 35 | mock.resetHistory(); 36 | expect(mock.history["get"]).to.eql([]); 37 | }); 38 | }); 39 | }); 40 | -------------------------------------------------------------------------------- /test/network_error.spec.js: -------------------------------------------------------------------------------- 1 | const axios = require("axios"); 2 | const expect = require("chai").expect; 3 | 4 | const MockAdapter = require("../src"); 5 | 6 | describe("networkError spec", function () { 7 | let instance; 8 | let mock; 9 | 10 | beforeEach(function () { 11 | instance = axios.create(); 12 | mock = new MockAdapter(instance); 13 | }); 14 | 15 | it("mocks networkErrors", function () { 16 | mock.onGet("/foo").networkError(); 17 | 18 | return instance.get("/foo").then( 19 | function () { 20 | expect.fail("should not be called"); 21 | }, 22 | function (error) { 23 | expect(error.config).to.exist; 24 | expect(error.response).to.not.exist; 25 | expect(error.message).to.equal("Network Error"); 26 | expect(error.isAxiosError).to.be.true; 27 | } 28 | ); 29 | }); 30 | 31 | it("can mock a network error only once", function () { 32 | mock.onGet("/foo").networkErrorOnce().onGet("/foo").reply(200); 33 | 34 | return instance 35 | .get("/foo") 36 | .then( 37 | function () {}, 38 | function () { 39 | return instance.get("/foo"); 40 | } 41 | ) 42 | .then(function (response) { 43 | expect(response.status).to.equal(200); 44 | }); 45 | }); 46 | }); 47 | -------------------------------------------------------------------------------- /test/on_any.spec.js: -------------------------------------------------------------------------------- 1 | const axios = require("axios"); 2 | const expect = require("chai").expect; 3 | 4 | const MockAdapter = require("../src"); 5 | 6 | describe("MockAdapter onAny", function () { 7 | let instance; 8 | let mock; 9 | 10 | beforeEach(function () { 11 | instance = axios.create(); 12 | mock = new MockAdapter(instance); 13 | }); 14 | 15 | it("registers a handler for every HTTP method", function () { 16 | mock.onAny("/foo").reply(200); 17 | 18 | expect(mock.handlers["get"]).not.to.be.empty; 19 | expect(mock.handlers["post"]).not.to.be.empty; 20 | expect(mock.handlers["head"]).not.to.be.empty; 21 | expect(mock.handlers["delete"]).not.to.be.empty; 22 | expect(mock.handlers["patch"]).not.to.be.empty; 23 | expect(mock.handlers["put"]).not.to.be.empty; 24 | expect(mock.handlers["options"]).not.to.be.empty; 25 | expect(mock.handlers["list"]).not.to.be.empty; 26 | expect(mock.handlers["link"]).not.to.be.empty; 27 | expect(mock.handlers["unlink"]).not.to.be.empty; 28 | }); 29 | 30 | it("mocks any request with a matching url", function () { 31 | mock.onAny("/foo").reply(200); 32 | 33 | return instance 34 | .head("/foo") 35 | .then(function () { 36 | return instance.patch("/foo"); 37 | }) 38 | .then(function (response) { 39 | expect(response.status).to.equal(200); 40 | }); 41 | }); 42 | 43 | it("mocks any request with a matching url and body", function () { 44 | const body = [ 45 | { object: { with: { deep: "property" } }, array: ["1", "abc"] }, 46 | "a", 47 | ]; 48 | mock.onAny("/anyWithBody", { data: body }).reply(200); 49 | 50 | return instance 51 | .put("/anyWithBody", body) 52 | .then(function () { 53 | return instance.post("/anyWithBody", body); 54 | }) 55 | .then(function (response) { 56 | expect(response.status).to.equal(200); 57 | 58 | return instance.post("/anyWithBody") 59 | .then(function () { 60 | throw new Error("should not get here"); 61 | }) 62 | .catch(function (err) { 63 | expect(err.response.status).to.equal(404); 64 | }); 65 | }); 66 | }); 67 | 68 | it("removes all handlers after replying with replyOnce", function () { 69 | mock.onAny("/foo").replyOnce(200); 70 | 71 | return instance.get("/foo").then(function () { 72 | expect(mock.handlers["get"]).to.be.empty; 73 | expect(mock.handlers["post"]).to.be.empty; 74 | }); 75 | }); 76 | }); 77 | -------------------------------------------------------------------------------- /test/pass_through.spec.js: -------------------------------------------------------------------------------- 1 | const axios = require("axios"); 2 | const expect = require("chai").expect; 3 | const createServer = require("http").createServer; 4 | 5 | const MockAdapter = require("../src"); 6 | 7 | describe("passThrough tests (requires Node)", function () { 8 | let instance; 9 | let mock; 10 | let httpServer; 11 | let serverUrl; 12 | 13 | before("set up Node server", function () { 14 | return new Promise(function (resolve, reject) { 15 | httpServer = createServer(function (req, resp) { 16 | if (req.url === "/error") { 17 | resp.statusCode = 500; 18 | resp.end(); 19 | } else { 20 | resp.statusCode = 200; 21 | // Reply with path 22 | resp.end(req.url, "utf8"); 23 | } 24 | }) 25 | .listen(0, "127.0.0.1", function () { 26 | serverUrl = `http://127.0.0.1:${httpServer.address().port}`; 27 | resolve(); 28 | }) 29 | .on("error", reject); 30 | }); 31 | }); 32 | 33 | after(function () { 34 | httpServer.close(); 35 | }); 36 | 37 | beforeEach(function () { 38 | instance = axios.create({ baseURL: serverUrl }); 39 | mock = new MockAdapter(instance); 40 | }); 41 | 42 | it("allows selective mocking", function () { 43 | mock.onGet("/foo").reply(200, "bar"); 44 | mock.onGet("/error").reply(200, "success"); 45 | mock.onGet("/bar").passThrough(); 46 | 47 | return Promise.all([ 48 | instance.get("/foo").then(function (response) { 49 | expect(response.status).to.equal(200); 50 | expect(response.data).to.equal("bar"); 51 | }), 52 | instance.get("/error").then(function (response) { 53 | expect(response.status).to.equal(200); 54 | expect(response.data).to.equal("success"); 55 | }), 56 | instance.get("/bar").then(function (response) { 57 | expect(response.status).to.equal(200); 58 | expect(response.data).to.equal("/bar"); 59 | }), 60 | instance 61 | .get("/noHandler") 62 | .then(function (response) { 63 | // Mock adapter should return an error 64 | expect(true).to.be.false; 65 | }) 66 | .catch(function (error) { 67 | expect(error).to.have.nested.property("response.status", 404); 68 | }), 69 | ]); 70 | }); 71 | 72 | it("handles errors correctly", function () { 73 | mock.onGet("/error").passThrough(); 74 | 75 | return instance 76 | .get("/error") 77 | .then(function () { 78 | // The server should've returned an error 79 | expect(false).to.be.true; 80 | }) 81 | .catch(function (error) { 82 | expect(error).to.have.nested.property("response.status", 500); 83 | }); 84 | }); 85 | 86 | it("allows setting default passThrough handler", function () { 87 | mock.onGet("/foo").reply(200, "bar").onAny().passThrough(); 88 | 89 | const randomPath = `xyz${Math.round(10000 * Math.random())}`; 90 | 91 | return Promise.all([ 92 | instance.get("/foo").then(function (response) { 93 | expect(response.status).to.equal(200); 94 | expect(response.data).to.equal("bar"); 95 | }), 96 | instance.get(`/${randomPath}`).then(function (response) { 97 | expect(response.status).to.equal(200); 98 | expect(response.data).to.equal(`/${randomPath}`); 99 | }), 100 | instance.post("/post").then(function (response) { 101 | expect(response.status).to.equal(200); 102 | expect(response.data).to.equal("/post"); 103 | }), 104 | ]); 105 | }); 106 | 107 | it("handles baseURL correctly", function () { 108 | instance = axios.create({ 109 | baseURL: "http://localhost/test", 110 | proxy: { 111 | host: "127.0.0.1", 112 | port: httpServer.address().port, 113 | }, 114 | }); 115 | mock = new MockAdapter(instance); 116 | 117 | mock.onAny().passThrough(); 118 | return instance.get("/foo").then(function (response) { 119 | expect(response.status).to.equal(200); 120 | expect(response.data).to.equal("http://localhost/test/foo"); 121 | }); 122 | }); 123 | 124 | it("handle request with baseURL only", function () { 125 | mock.onAny().passThrough(); 126 | 127 | return instance.get(undefined).then(function (response) { 128 | expect(response.data).to.equal("/"); 129 | }); 130 | }); 131 | 132 | it("handles request transformations properly", function () { 133 | mock.onGet("/foo").passThrough(); 134 | 135 | return instance 136 | .get("/foo", { 137 | data: "foo", 138 | transformRequest: [ 139 | function (data) { 140 | return `${data}foo`; 141 | }, 142 | ], 143 | }) 144 | .then(function (response) { 145 | expect(response.config.data).to.equal("foofoo"); 146 | }); 147 | }); 148 | 149 | it("handles response transformations properly", function () { 150 | mock.onGet("/foo").passThrough(); 151 | 152 | return instance 153 | .get("/foo", { 154 | transformResponse: [ 155 | function (data) { 156 | return `${data}foo`; 157 | }, 158 | ], 159 | }) 160 | .then(function (response) { 161 | expect(response.data).to.equal("/foofoo"); 162 | }); 163 | }); 164 | 165 | it("applies interceptors only once", function () { 166 | mock.onGet("/foo").passThrough(); 167 | let requestCount = 0; 168 | let responseCount = 0; 169 | instance.interceptors.request.use(function (config) { 170 | requestCount++; 171 | return config; 172 | }); 173 | 174 | instance.interceptors.response.use(function (config) { 175 | responseCount++; 176 | return config; 177 | }); 178 | 179 | return instance.get("/foo") 180 | .then(function () { 181 | expect(requestCount).to.equal(1); 182 | expect(responseCount).to.equal(1); 183 | }); 184 | }); 185 | }); 186 | -------------------------------------------------------------------------------- /test/pass_through_on_no_match.spec.js: -------------------------------------------------------------------------------- 1 | const axios = require("axios"); 2 | const expect = require("chai").expect; 3 | const createServer = require("http").createServer; 4 | 5 | const MockAdapter = require("../src"); 6 | 7 | describe("onNoMatch=passthrough option tests (requires Node)", function () { 8 | let instance; 9 | let mock; 10 | let httpServer; 11 | let serverUrl; 12 | 13 | before("set up Node server", function () { 14 | return new Promise(function (resolve, reject) { 15 | httpServer = createServer(function (req, resp) { 16 | if (req.url === "/error") { 17 | resp.statusCode = 500; 18 | resp.end(); 19 | } else { 20 | resp.statusCode = 200; 21 | // Reply with path 22 | resp.end(req.url, "utf8"); 23 | } 24 | }) 25 | .listen(0, "127.0.0.1", function () { 26 | serverUrl = `http://127.0.0.1:${httpServer.address().port}`; 27 | resolve(); 28 | }) 29 | .on("error", reject); 30 | }); 31 | }); 32 | 33 | after(function () { 34 | httpServer.close(); 35 | }); 36 | 37 | beforeEach(function () { 38 | instance = axios.create({ baseURL: serverUrl }); 39 | mock = new MockAdapter(instance, { onNoMatch: "passthrough" }); 40 | }); 41 | 42 | it("works correctly if set no handlers", function () { 43 | const randomPath = `xyz${Math.round(10000 * Math.random())}`; 44 | 45 | return Promise.all([ 46 | instance.get(`/${randomPath}`).then(function (response) { 47 | expect(response.status).to.equal(200); 48 | expect(response.data).to.equal(`/${randomPath}`); 49 | }), 50 | ]); 51 | }); 52 | 53 | it("allows selective mocking", function () { 54 | mock.onGet("/foo").reply(200, "bar"); 55 | mock.onGet("/error").reply(200, "success"); 56 | mock.onGet("/bar").passThrough(); 57 | 58 | const randomPath = `xyz${Math.round(10000 * Math.random())}`; 59 | 60 | return Promise.all([ 61 | instance.get("/foo").then(function (response) { 62 | expect(response.status).to.equal(200); 63 | expect(response.data).to.equal("bar"); 64 | }), 65 | instance.get("/error").then(function (response) { 66 | expect(response.status).to.equal(200); 67 | expect(response.data).to.equal("success"); 68 | }), 69 | instance.get("/bar").then(function (response) { 70 | expect(response.status).to.equal(200); 71 | expect(response.data).to.equal("/bar"); 72 | }), 73 | instance.get(`/${randomPath}`).then(function (response) { 74 | expect(response.status).to.equal(200); 75 | expect(response.data).to.equal(`/${randomPath}`); 76 | }), 77 | ]); 78 | }); 79 | 80 | it("handles errors correctly", function () { 81 | return instance 82 | .get("/error") 83 | .then(function () { 84 | // The server should've returned an error 85 | expect(false).to.be.true; 86 | }) 87 | .catch(function (error) { 88 | expect(error).to.have.nested.property("response.status", 500); 89 | }); 90 | }); 91 | 92 | it("setting passThrough handler don't break anything", function () { 93 | mock.onGet("/foo").reply(200, "bar").onAny().passThrough(); 94 | 95 | const randomPath = `xyz${Math.round(10000 * Math.random())}`; 96 | 97 | return Promise.all([ 98 | instance.get("/foo").then(function (response) { 99 | expect(response.status).to.equal(200); 100 | expect(response.data).to.equal("bar"); 101 | }), 102 | instance.get(`/${randomPath}`).then(function (response) { 103 | expect(response.status).to.equal(200); 104 | expect(response.data).to.equal(`/${randomPath}`); 105 | }), 106 | instance.post("/post").then(function (response) { 107 | expect(response.status).to.equal(200); 108 | expect(response.data).to.equal("/post"); 109 | }), 110 | ]); 111 | }); 112 | 113 | it("handles baseURL correctly", function () { 114 | instance = axios.create({ 115 | baseURL: "http://localhost/test", 116 | proxy: { 117 | host: "127.0.0.1", 118 | port: httpServer.address().port, 119 | }, 120 | }); 121 | mock = new MockAdapter(instance, { onNoMatch: "passthrough" }); 122 | 123 | return instance.get("/foo").then(function (response) { 124 | expect(response.status).to.equal(200); 125 | expect(response.data).to.equal("http://localhost/test/foo"); 126 | }); 127 | }); 128 | 129 | it("handles request transformations properly", function () { 130 | return instance 131 | .get("/foo", { 132 | data: "foo", 133 | transformRequest: [ 134 | function (data) { 135 | return `${data}foo`; 136 | }, 137 | ], 138 | }) 139 | .then(function (response) { 140 | expect(response.config.data).to.equal("foofoo"); 141 | }); 142 | }); 143 | 144 | it("handles response transformations properly", function () { 145 | return instance 146 | .get("/foo", { 147 | transformResponse: [ 148 | function (data) { 149 | return `${data}foo`; 150 | }, 151 | ], 152 | }) 153 | .then(function (response) { 154 | expect(response.data).to.equal("/foofoo"); 155 | }); 156 | }); 157 | }); 158 | -------------------------------------------------------------------------------- /test/promise.spec.js: -------------------------------------------------------------------------------- 1 | const axios = require("axios"); 2 | const expect = require("chai").expect; 3 | 4 | const MockAdapter = require("../src"); 5 | 6 | describe("MockAdapter reply with Promise", function () { 7 | let instance; 8 | let mock; 9 | 10 | beforeEach(function () { 11 | instance = axios.create(); 12 | mock = new MockAdapter(instance); 13 | }); 14 | 15 | it("allows resolving with Promise", function () { 16 | mock.onGet("/promise").reply(function () { 17 | return new Promise(function (resolve, reject) { 18 | resolve([200, { bar: "fooPromised" }]); 19 | }); 20 | }); 21 | 22 | return instance 23 | .get("/promise") 24 | .then(function (response) { 25 | expect(response.status).to.equal(200); 26 | expect(response.data.bar).to.equal("fooPromised"); 27 | }) 28 | .catch(function () { 29 | expect(true).to.be.false; 30 | }); 31 | }); 32 | 33 | it("rejects after Promise resolves to error response", function () { 34 | mock.onGet("/bad/promise").reply(function () { 35 | return new Promise(function (resolve) { 36 | resolve([400, { bad: "request" }]); 37 | }); 38 | }); 39 | 40 | return instance 41 | .get("/bad/promise") 42 | .then(function (response) { 43 | expect(true).to.be.false; 44 | }) 45 | .catch(function (error) { 46 | expect(error).to.have.nested.property("response.status", 400); 47 | expect(error).to.have.nested.property("response.data.bad", "request"); 48 | }); 49 | }); 50 | 51 | it("passes rejecting Promise verbatim", function () { 52 | mock.onGet("/reject").reply(function () { 53 | return new Promise(function (resolve, reject) { 54 | // eslint-disable-next-line prefer-promise-reject-errors 55 | reject({ custom: "error" }); 56 | }); 57 | }); 58 | 59 | return instance 60 | .get("/reject") 61 | .then(function (response) { 62 | expect(true).to.be.false; 63 | }) 64 | .catch(function (error) { 65 | expect(error).to.deep.equal({ custom: "error" }); 66 | }); 67 | }); 68 | }); 69 | -------------------------------------------------------------------------------- /test/reply_once.spec.js: -------------------------------------------------------------------------------- 1 | const axios = require("axios"); 2 | const expect = require("chai").expect; 3 | 4 | const MockAdapter = require("../src"); 5 | 6 | describe("MockAdapter replyOnce", function () { 7 | let instance; 8 | let mock; 9 | 10 | beforeEach(function () { 11 | instance = axios.create(); 12 | mock = new MockAdapter(instance); 13 | }); 14 | 15 | it("supports chaining", function () { 16 | mock 17 | .onGet("/foo") 18 | .replyOnce(200) 19 | .onAny("/foo") 20 | .replyOnce(500) 21 | .onPost("/foo") 22 | .replyOnce(201); 23 | 24 | expect(mock.handlers["get"].length).to.equal(2); 25 | expect(mock.handlers["post"].length).to.equal(2); 26 | }); 27 | 28 | it("replies as normally on the first call", function () { 29 | mock.onGet("/foo").replyOnce(200, { 30 | foo: "bar", 31 | }); 32 | 33 | return instance.get("/foo").then(function (response) { 34 | expect(response.status).to.equal(200); 35 | expect(response.data.foo).to.equal("bar"); 36 | }); 37 | }); 38 | 39 | it("replies only once", function () { 40 | let called = false; 41 | mock.onGet("/foo").replyOnce(200); 42 | 43 | return instance 44 | .get("/foo") 45 | .then(function () { 46 | called = true; 47 | return instance.get("/foo"); 48 | }) 49 | .catch(function (error) { 50 | expect(called).to.be.true; 51 | expect(error.response.status).to.equal(404); 52 | }); 53 | }); 54 | 55 | it("replies only once when used with onAny", function () { 56 | let called = false; 57 | mock.onAny("/foo").replyOnce(200); 58 | 59 | return instance 60 | .get("/foo") 61 | .then(function () { 62 | called = true; 63 | return instance.post("/foo"); 64 | }) 65 | .catch(function (error) { 66 | expect(called).to.be.true; 67 | expect(error.response.status).to.equal(404); 68 | }); 69 | }); 70 | 71 | it("replies only once when using request body matching", function () { 72 | let called = false; 73 | const body = "abc"; 74 | mock.onPost("/onceWithBody", body).replyOnce(200); 75 | 76 | return instance 77 | .post("/onceWithBody", body) 78 | .then(function () { 79 | called = true; 80 | return instance.post("/onceWithBody", body); 81 | }) 82 | .catch(function (error) { 83 | expect(called).to.be.true; 84 | expect(error.response.status).to.equal(404); 85 | }); 86 | }); 87 | 88 | it("replies only once when using a function that returns a response", function () { 89 | mock 90 | .onGet("/foo") 91 | .replyOnce(function () { 92 | return [200]; 93 | }) 94 | .onGet("/foo") 95 | .replyOnce(function () { 96 | return [202]; 97 | }); 98 | 99 | return instance 100 | .get("/foo") 101 | .then(function (response) { 102 | expect(response.status).to.equal(200); 103 | return instance.get("/foo"); 104 | }) 105 | .then(function (response) { 106 | expect(response.status).to.equal(202); 107 | }); 108 | }); 109 | }); 110 | -------------------------------------------------------------------------------- /test/throw_exception_on_no_match.spec.js: -------------------------------------------------------------------------------- 1 | const axios = require("axios"); 2 | const expect = require("chai").expect; 3 | 4 | const MockAdapter = require("../src"); 5 | 6 | describe("onNoMatch=throwException option tests (requires Node)", function () { 7 | let instance; 8 | let mock; 9 | 10 | beforeEach(function () { 11 | instance = axios.create(); 12 | mock = new MockAdapter(instance, { onNoMatch: "throwException" }); 13 | }); 14 | 15 | it("allows selective mocking", function () { 16 | mock.onGet("/foo").reply(200, "bar"); 17 | mock.onGet("/error").reply(200, "success"); 18 | 19 | return Promise.all([ 20 | instance.get("/foo").then(function (response) { 21 | expect(response.status).to.equal(200); 22 | expect(response.data).to.equal("bar"); 23 | }), 24 | instance.get("/error").then(function (response) { 25 | expect(response.status).to.equal(200); 26 | expect(response.data).to.equal("success"); 27 | }), 28 | ]); 29 | }); 30 | 31 | it("handles errors correctly when could not find mock for requested url", function () { 32 | const expectedUrl = "http://127.0.0.1/unexistent_path"; 33 | const expectedMethod = "get"; 34 | 35 | return instance 36 | .get(expectedUrl) 37 | .then(function () { 38 | // The server should've returned an error 39 | expect(false).to.be.true; 40 | }) 41 | .catch(function (error) { 42 | expect(error).to.have.nested.property("isCouldNotFindMockError", true); 43 | expect(error).to.have.nested.property("method", expectedMethod); 44 | expect(error).to.have.nested.property("url", expectedUrl); 45 | expect(error.message).to.contain("Could not find mock for"); 46 | expect(error.message).to.contain(expectedMethod); 47 | expect(error.message).to.contain(expectedUrl); 48 | }); 49 | }); 50 | }); 51 | -------------------------------------------------------------------------------- /test/timeout.spec.js: -------------------------------------------------------------------------------- 1 | const axios = require("axios"); 2 | const expect = require("chai").expect; 3 | 4 | const MockAdapter = require("../src"); 5 | 6 | describe("timeout spec", function () { 7 | let instance; 8 | let mock; 9 | 10 | beforeEach(function () { 11 | instance = axios.create(); 12 | mock = new MockAdapter(instance); 13 | }); 14 | 15 | it("mocks timeout response", function () { 16 | mock.onGet("/foo").timeout(); 17 | 18 | return instance.get("/foo").then( 19 | function () { 20 | expect.fail("should not be called"); 21 | }, 22 | function (error) { 23 | expect(error.config).to.exist; 24 | expect(error.code).to.equal("ECONNABORTED"); 25 | expect(error.message).to.equal("timeout of 0ms exceeded"); 26 | expect(error.isAxiosError).to.be.true; 27 | } 28 | ); 29 | }); 30 | 31 | it("can timeout only once", function () { 32 | mock.onGet("/foo").timeoutOnce().onGet("/foo").reply(200); 33 | 34 | return instance 35 | .get("/foo") 36 | .then( 37 | function () {}, 38 | function () { 39 | return instance.get("/foo"); 40 | } 41 | ) 42 | .then(function (response) { 43 | expect(response.status).to.equal(200); 44 | }); 45 | }); 46 | 47 | it("responds with timeoutErrorMessage", function () { 48 | mock.onGet("/foo").timeout(); 49 | const timeoutErrorMessage = "That request sure did time out"; 50 | 51 | return instance 52 | .get("/foo", { 53 | timeoutErrorMessage: timeoutErrorMessage, 54 | }) 55 | .then( 56 | function () { 57 | expect.fail("should not be called"); 58 | }, 59 | function (error) { 60 | expect(error.config).to.exist; 61 | expect(error.code).to.equal("ECONNABORTED"); 62 | expect(error.message).to.equal(timeoutErrorMessage); 63 | expect(error.isAxiosError).to.be.true; 64 | } 65 | ); 66 | }); 67 | }); 68 | -------------------------------------------------------------------------------- /test/trailing_slash_baseurl.spec.js: -------------------------------------------------------------------------------- 1 | const axios = require("axios"); 2 | const expect = require("chai").expect; 3 | const createServer = require("http").createServer; 4 | 5 | const MockAdapter = require("../src"); 6 | 7 | describe("trailing slash in axios baseUrl issue (requires Node)", function () { 8 | let instance; 9 | let mock; 10 | let httpServer; 11 | let serverUrl; 12 | 13 | before("set up Node server", function () { 14 | return new Promise(function (resolve, reject) { 15 | httpServer = createServer(function (req, resp) { 16 | if (req.url === "/error") { 17 | resp.statusCode = 500; 18 | resp.end(); 19 | } else { 20 | resp.statusCode = 200; 21 | // Reply with path minus leading / 22 | resp.end(req.url.slice(1), "utf8"); 23 | } 24 | }) 25 | .listen(0, "127.0.0.1", function () { 26 | serverUrl = `http://127.0.0.1:${httpServer.address().port}`; 27 | resolve(); 28 | }) 29 | .on("error", reject); 30 | }); 31 | }); 32 | 33 | after(function () { 34 | httpServer.close(); 35 | }); 36 | 37 | beforeEach(function () { 38 | instance = axios.create({ baseURL: `${serverUrl}/` }); // baseUrl has a trailing slash 39 | mock = new MockAdapter(instance); 40 | }); 41 | 42 | it("axios should handle trailing slash in baseUrl", function () { 43 | // passes 44 | mock.onAny().passThrough(); 45 | return Promise.all([ 46 | instance.get("/foo").then(function (response) { 47 | expect(response.status).to.equal(200); 48 | expect(response.data).to.equal("foo"); 49 | }), 50 | instance.get("foo").then(function (response) { 51 | expect(response.status).to.equal(200); 52 | expect(response.data).to.equal("foo"); 53 | }), 54 | ]); 55 | }); 56 | 57 | it("mock adapter should handle trailing slash in baseUrl", function () { 58 | // both fail: 404 59 | mock.onGet("/foo").reply(200, "bar"); 60 | return Promise.all([ 61 | instance.get("/foo").then(function (response) { 62 | expect(response.status).to.equal(200); 63 | expect(response.data).to.equal("bar"); 64 | }), 65 | instance.get("foo").then(function (response) { 66 | expect(response.status).to.equal(200); 67 | expect(response.data).to.equal("bar"); 68 | }), 69 | ]); 70 | }); 71 | }); 72 | -------------------------------------------------------------------------------- /test/utils.spec.js: -------------------------------------------------------------------------------- 1 | const expect = require("chai").expect; 2 | const find = require("../src/utils").find; 3 | const isEqual = require("../src/utils").isEqual; 4 | const isObjectOrArray = require("../src/utils").isObjectOrArray; 5 | const isBlob = require("../src/utils").isBlob; 6 | const isBodyOrParametersMatching = require("../src/utils").isBodyOrParametersMatching; 7 | 8 | describe("utility functions", function () { 9 | context("find", function () { 10 | it("returns the value for which the predicate holds true", function () { 11 | const array = [1, 2, 3]; 12 | const value = find(array, function (value) { 13 | return value === 2; 14 | }); 15 | expect(value).to.equal(2); 16 | }); 17 | 18 | it("returns the first value for which the predicate holds true", function () { 19 | const array = [ 20 | { key: 1, value: "one" }, 21 | { key: 1, value: "two" }, 22 | ]; 23 | const value = find(array, function (value) { 24 | return value.key === 1; 25 | }); 26 | expect(value.value).to.equal("one"); 27 | }); 28 | 29 | it("returns undefined if the value is not found", function () { 30 | const array = [1, 2, 3]; 31 | const value = find(array, function (value) { 32 | return value === 4; 33 | }); 34 | expect(value).to.be.undefined; 35 | }); 36 | }); 37 | 38 | context("isEqual", function () { 39 | it("checks with strict equality", function () { 40 | const a = { foo: "5" }; 41 | const b = { foo: 5 }; 42 | expect(isEqual(a, b)).to.be.false; 43 | }); 44 | }); 45 | 46 | context("isObjectOrArray", function () { 47 | it("returns true for plain objects", function () { 48 | expect(isObjectOrArray({ foo: "bar" })).to.be.true; 49 | }); 50 | 51 | it("returns true for arrays", function () { 52 | expect(isObjectOrArray([1, 2, 3])).to.be.true; 53 | }); 54 | 55 | it("returns false for anything that is not an object or array", function () { 56 | expect(isObjectOrArray(true)).to.be.false; 57 | expect(isObjectOrArray(false)).to.be.false; 58 | expect(isObjectOrArray(null)).to.be.false; 59 | expect(isObjectOrArray(undefined)).to.be.false; 60 | expect(isObjectOrArray(function () {})).to.be.false; 61 | expect(isObjectOrArray(0)).to.be.false; 62 | expect(isObjectOrArray(1)).to.be.false; 63 | expect(isObjectOrArray("")).to.be.false; 64 | expect(isObjectOrArray(" ")).to.be.false; 65 | expect(isObjectOrArray("1")).to.be.false; 66 | }); 67 | }); 68 | 69 | context("isBlob", function () { 70 | it("returns false for anything that is not a Blob", function () { 71 | expect(isBlob(true)).to.be.false; 72 | expect(isBlob(false)).to.be.false; 73 | expect(isBlob(null)).to.be.false; 74 | expect(isBlob(undefined)).to.be.false; 75 | expect(isBlob(function () {})).to.be.false; 76 | expect(isBlob(0)).to.be.false; 77 | expect(isBlob(1)).to.be.false; 78 | expect(isBlob("")).to.be.false; 79 | expect(isBlob(" ")).to.be.false; 80 | expect(isBlob("1")).to.be.false; 81 | expect(isBlob({ foo: "bar" })).to.be.false; 82 | expect(isBlob([1, 2, 3])).to.be.false; 83 | }); 84 | }); 85 | 86 | context("isBodyOrParametersMatching", function() { 87 | it("delete has params only", function () { 88 | expect(isBodyOrParametersMatching(null, { "a": 2 }, { "params": { "a": 2 } } )).to.be.true; 89 | expect(isBodyOrParametersMatching(null, { "a": 2 }, { "params": { "b": 2 } } )).to.be.false; 90 | }); 91 | it("delete has data only", function () { 92 | expect(isBodyOrParametersMatching({ "x": 1 }, null, { "data": { "x": 1 } })).to.be.true; 93 | expect(isBodyOrParametersMatching({ "x": 1 }, null, { "data": { "y": 1 } })).to.be.false; 94 | }); 95 | it("delete has body and params", function () { 96 | expect(isBodyOrParametersMatching({ "x": 1 }, { "a": 2 }, { "data": { "x": 1 }, "params": { "a": 2 } })).to.be.true; 97 | expect(isBodyOrParametersMatching({ "x": 1 }, { "a": 2 }, { "data": { "x": 1 }, "params": { "b": 2 } })).to.be.false; 98 | expect(isBodyOrParametersMatching({ "x": 1 }, { "a": 2 }, { "data": { "y": 1 }, "params": { "a": 2 } })).to.be.false; 99 | }); 100 | }); 101 | }); 102 | -------------------------------------------------------------------------------- /types/index.d.ts: -------------------------------------------------------------------------------- 1 | import { AxiosAdapter, AxiosInstance, AxiosRequestConfig } from 'axios'; 2 | 3 | interface AxiosHeaders { 4 | [key: string]: string | number | boolean | null | undefined; 5 | } 6 | 7 | type MockArrayResponse = [ 8 | status: number, 9 | data?: any, 10 | headers?: AxiosHeaders 11 | ]; 12 | 13 | type MockObjectResponse = { 14 | status: number; 15 | data: any; 16 | headers?: AxiosHeaders, 17 | config?: AxiosRequestConfig 18 | }; 19 | 20 | type MockResponse = MockArrayResponse | MockObjectResponse; 21 | 22 | type CallbackResponseSpecFunc = ( 23 | config: AxiosRequestConfig 24 | ) => MockResponse | Promise; 25 | 26 | type ResponseSpecFunc = ( 27 | statusOrCallback: number | CallbackResponseSpecFunc, 28 | data?: T, 29 | headers?: AxiosHeaders 30 | ) => MockAdapter; 31 | 32 | declare namespace MockAdapter { 33 | export interface RequestHandler { 34 | withDelayInMs(delay: number): RequestHandler; 35 | reply: ResponseSpecFunc; 36 | replyOnce: ResponseSpecFunc; 37 | passThrough(): MockAdapter; 38 | abortRequest(): MockAdapter; 39 | abortRequestOnce(): MockAdapter; 40 | networkError(): MockAdapter; 41 | networkErrorOnce(): MockAdapter; 42 | timeout(): MockAdapter; 43 | timeoutOnce(): MockAdapter; 44 | } 45 | } 46 | 47 | interface MockAdapterOptions { 48 | delayResponse?: number; 49 | onNoMatch?: 'passthrough' | 'throwException'; 50 | } 51 | 52 | interface AsymmetricMatcher { 53 | asymmetricMatch: Function; 54 | } 55 | 56 | interface ParamsMatcher { 57 | [param: string]: any; 58 | } 59 | 60 | interface HeadersMatcher { 61 | [header: string]: string; 62 | } 63 | 64 | type UrlMatcher = string | RegExp; 65 | type AsymmetricParamsMatcher = AsymmetricMatcher | ParamsMatcher; 66 | type AsymmetricHeadersMatcher = AsymmetricMatcher | HeadersMatcher; 67 | type AsymmetricRequestDataMatcher = AsymmetricMatcher | any; 68 | 69 | interface ConfigMatcher { 70 | params?: AsymmetricParamsMatcher; 71 | headers?: AsymmetricHeadersMatcher; 72 | data?: AsymmetricRequestDataMatcher; 73 | } 74 | 75 | type RequestMatcherFunc = ( 76 | matcher?: UrlMatcher, 77 | body?: AsymmetricRequestDataMatcher, 78 | config?: ConfigMatcher 79 | ) => MockAdapter.RequestHandler; 80 | 81 | type NoBodyRequestMatcherFunc = ( 82 | matcher?: UrlMatcher, 83 | config?: ConfigMatcher 84 | ) => MockAdapter.RequestHandler; 85 | 86 | type verb = 87 | | 'get' 88 | | 'post' 89 | | 'put' 90 | | 'delete' 91 | | 'patch' 92 | | 'options' 93 | | 'head' 94 | | 'list' 95 | | 'link' 96 | | 'unlink'; 97 | 98 | type HistoryArray = AxiosRequestConfig[] & Record 99 | 100 | declare class MockAdapter { 101 | static default: typeof MockAdapter; 102 | 103 | constructor(axiosInstance: AxiosInstance, options?: MockAdapterOptions); 104 | 105 | adapter(): AxiosAdapter; 106 | reset(): void; 107 | resetHandlers(): void; 108 | resetHistory(): void; 109 | restore(): void; 110 | 111 | history: HistoryArray; 112 | 113 | onAny: NoBodyRequestMatcherFunc; 114 | onGet: NoBodyRequestMatcherFunc; 115 | onDelete: NoBodyRequestMatcherFunc; 116 | onHead: NoBodyRequestMatcherFunc; 117 | onOptions: NoBodyRequestMatcherFunc; 118 | onPost: RequestMatcherFunc; 119 | onPut: RequestMatcherFunc; 120 | onPatch: RequestMatcherFunc; 121 | onList: RequestMatcherFunc; 122 | onLink: RequestMatcherFunc; 123 | onUnlink: RequestMatcherFunc; 124 | } 125 | 126 | export = MockAdapter; 127 | -------------------------------------------------------------------------------- /types/test.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import MockAdapter = require('axios-mock-adapter'); 3 | 4 | const instance = axios.create(); 5 | let mock = new MockAdapter(instance); 6 | mock = new MockAdapter.default(instance) 7 | 8 | namespace AllowsConstructing { 9 | new MockAdapter(instance); 10 | } 11 | 12 | namespace AllowsConstructingWithOptions { 13 | new MockAdapter(instance, { 14 | delayResponse: 2000, 15 | onNoMatch: 'passthrough' 16 | }); 17 | } 18 | 19 | namespace SupportsOnNoMatchThrowException { 20 | new MockAdapter(instance, { 21 | onNoMatch: 'throwException' 22 | }); 23 | } 24 | 25 | namespace ExposesAdapter { 26 | mock.adapter(); 27 | } 28 | 29 | namespace SupportsReset { 30 | mock.reset(); 31 | } 32 | 33 | namespace SupportsResetHandlers { 34 | mock.resetHandlers(); 35 | } 36 | 37 | namespace SupportsResetHistory { 38 | mock.resetHistory(); 39 | } 40 | 41 | namespace SupportsHistoryArray { 42 | mock.history.length; 43 | mock.history[0].method; 44 | mock.history[0].url; 45 | mock.history[0].params; 46 | mock.history[0].data; 47 | mock.history[0].headers; 48 | 49 | mock.history.get[0].method; 50 | mock.history.get[0].url; 51 | mock.history.get[0].params; 52 | mock.history.get[0].data; 53 | mock.history.get[0].headers; 54 | } 55 | 56 | namespace SupportsRestore { 57 | mock.restore(); 58 | } 59 | 60 | namespace SupportsAllHttpVerbs { 61 | mock.onGet; 62 | mock.onPost; 63 | mock.onPut; 64 | mock.onHead; 65 | mock.onDelete; 66 | mock.onPatch; 67 | mock.onList; 68 | mock.onLink; 69 | mock.onUnlink; 70 | } 71 | 72 | namespace SupportsAnyVerb { 73 | mock.onAny; 74 | } 75 | 76 | namespace AllowsVerbOnlyMatcher { 77 | mock.onGet(); 78 | } 79 | 80 | namespace AllowsUrlMatcher { 81 | mock.onGet('/foo'); 82 | } 83 | 84 | namespace AllowsUrlRegExpMatcher { 85 | mock.onGet(/\/fo+/); 86 | } 87 | 88 | namespace AllowsStringBodyMatcher { 89 | mock.onPatch('/foo', 'bar'); 90 | } 91 | 92 | namespace AllowsBodyMatcher { 93 | mock.onPost('/foo', {id: 4, name: 'foo'}); 94 | mock.onPut('/foo', {id: 4, name: 'foo'}); 95 | mock.onAny('/foo', {data: {id: 4, name: 'foo'}}); 96 | } 97 | 98 | namespace AllowsParamsMatcher { 99 | mock.onGet('/foo', {params: {searchText: 'John'}}); 100 | mock.onDelete('/foo', {params: {searchText: 'John'}}); 101 | } 102 | 103 | namespace AllowsReplyWithStatus { 104 | mock.onGet().reply(200); 105 | } 106 | 107 | namespace SupportsReplyOnce { 108 | mock.onGet().replyOnce(200); 109 | } 110 | 111 | namespace SupportsPassThrough { 112 | mock.onGet().passThrough(); 113 | } 114 | 115 | namespace SupportsTimeout { 116 | mock.onGet().timeout(); 117 | } 118 | 119 | namespace SupportsTimeoutOnce { 120 | mock.onGet().timeoutOnce(); 121 | } 122 | 123 | namespace SupportsAbortRequest { 124 | mock.onGet().abortRequest(); 125 | } 126 | 127 | namespace SupportsAbortRequestOnce { 128 | mock.onGet().abortRequestOnce(); 129 | } 130 | 131 | namespace SupportsNetworkError { 132 | mock.onGet().networkError(); 133 | } 134 | 135 | namespace SupportsNetworkErrorOnce { 136 | mock.onGet().networkErrorOnce(); 137 | } 138 | 139 | namespace withDelayInMs { 140 | mock.onGet().withDelayInMs(2000).reply(200, { data: 'foo' }); 141 | } 142 | 143 | namespace AllowsFunctionReply { 144 | mock.onGet().reply(config => { 145 | return [200, { data: 'foo' }, { RequestedURL: config.url }]; 146 | }); 147 | } 148 | 149 | namespace AllowsPromiseReply { 150 | mock.onGet().reply(config => { 151 | return Promise.resolve([ 152 | 200, 153 | { data: 'bar' }, 154 | { RequestedURL: config.url } 155 | ]); 156 | }); 157 | } 158 | 159 | namespace SupportsChaining { 160 | mock 161 | .onGet('/users') 162 | .reply(200, []) 163 | .onGet('/posts') 164 | .reply(200, []); 165 | } 166 | 167 | namespace ExportsRequestHandlerInterface { 168 | const handler: MockAdapter.RequestHandler = mock.onAny(); 169 | handler.reply(200); 170 | } 171 | -------------------------------------------------------------------------------- /types/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "lib": [ 5 | "es6" 6 | ], 7 | "noEmit": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "noImplicitAny": true, 10 | "noImplicitThis": true, 11 | "strictNullChecks": true, 12 | "baseUrl": ".", 13 | "paths": { 14 | "axios-mock-adapter": [ 15 | "." 16 | ] 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function (env, argv) { 2 | return { 3 | output: { 4 | library: "AxiosMockAdapter", 5 | libraryTarget: "umd", 6 | filename: 7 | argv.mode === "production" 8 | ? "axios-mock-adapter.min.js" 9 | : "axios-mock-adapter.js", 10 | }, 11 | externals: { 12 | axios: "axios", 13 | }, 14 | plugins: [], 15 | }; 16 | }; 17 | --------------------------------------------------------------------------------