diff --git a/lib/mock-axios.ts b/lib/mock-axios.ts index f73ce9d..1de6c77 100644 --- a/lib/mock-axios.ts +++ b/lib/mock-axios.ts @@ -16,261 +16,271 @@ import { HttpResponse, } from "./mock-axios-types"; -/** a FIFO queue of pending request */ -const _pending_requests: AxiosMockQueueItem[] = []; - -const _newReq: (config?: any) => UnresolvedSynchronousPromise = (config: any = {}, actualConfig: any = {}) => { - if(typeof config === 'string') { - // Allow for axios('example/url'[, config]) - actualConfig.url = config; - config = actualConfig; - } - - const method: string = config.method || "get"; - const url: string = config.url; - const data: any = config.data; - const promise: UnresolvedSynchronousPromise = SynchronousPromise.unresolved(); - if(config.cancelToken) { - config.cancelToken.promise.then((cancel: any) => { - // check if promise is still waiting for an answer - if(_pending_requests.find(x => x.promise === promise)) { - MockAxios.mockError(cancel, promise) - } - }) +const _checkCriteria = (item: AxiosMockQueueItem, criteria: AxiosMockRequestCriteria) => { + if (criteria.method !== undefined && criteria.method.toLowerCase() !== item.method.toLowerCase()) { + return false; } - _pending_requests.push({ - config, - data, - method, - promise, - url - }); - return promise; -}; - -const _helperReq = (method: string, url: string, data?: any, config?: any) => { - const conf = data && config ? config : {}; - return _newReq({ - ...conf, - data, - method, - url, - }); -}; - -const _helperReqNoData = (method: string, url: string, config?: any) => { - return _helperReq(method, url, {}, config) -} - -const MockAxios: AxiosMockType = (jest.fn(_newReq) as unknown) as AxiosMockType; + if (criteria.url !== undefined && criteria.url !== item.url) { + return false; + } -// mocking Axios methods -MockAxios.get = jest.fn(_helperReqNoData.bind(null, "get")); -MockAxios.post = jest.fn(_helperReq.bind(null, "post")); -MockAxios.put = jest.fn(_helperReq.bind(null, "put")); -MockAxios.patch = jest.fn(_helperReq.bind(null, "patch")); -MockAxios.delete = jest.fn(_helperReqNoData.bind(null, "delete")); -MockAxios.request = jest.fn(_newReq); -MockAxios.all = jest.fn((values) => Promise.all(values)); -MockAxios.head = jest.fn(_helperReqNoData.bind(null, "head")); -MockAxios.options = jest.fn(_helperReqNoData.bind(null, "options")); -MockAxios.create = jest.fn(() => MockAxios); - -MockAxios.interceptors = { - request: { - use: jest.fn(), - eject: jest.fn(), - }, - response: { - use: jest.fn(), - eject: jest.fn(), - }, + return true; }; -MockAxios.defaults = { - headers: { - common: [], - }, -}; +export class MockAxiosInstance { -MockAxios.popPromise = (promise?: SynchronousPromise) => { - if (promise) { - // remove the promise from pending queue - for (let ix = 0; ix < _pending_requests.length; ix++) { - const req: AxiosMockQueueItem = _pending_requests[ix]; + /** a FIFO queue of pending request */ + private _pending_requests: AxiosMockQueueItem[] = []; - if (req.promise === promise) { - _pending_requests.splice(ix, 1); - return req.promise; - } + private _newReq: (config?: any) => UnresolvedSynchronousPromise = (config: any = {}, actualConfig: any = {}) => { + if (typeof config === 'string') { + // Allow for axios('example/url'[, config]) + actualConfig.url = config; + config = actualConfig; } - } else { - // take the oldest promise - const req: AxiosMockQueueItem = _pending_requests.shift(); - return req ? req.promise : void 0; - } -}; -MockAxios.popRequest = (request?: AxiosMockQueueItem) => { - if (request) { - const ix = _pending_requests.indexOf(request); - if (ix === -1) { - return void 0; + const method: string = config.method || "get"; + const url: string = config.url; + const data: any = config.data; + const promise: UnresolvedSynchronousPromise = SynchronousPromise.unresolved(); + + if (config.cancelToken) { + config.cancelToken.promise.then((cancel: any) => { + // check if promise is still waiting for an answer + if (this._pending_requests.find(x => x.promise === promise)) { + this.mockError(cancel, promise) + } + }) } - _pending_requests.splice(ix, 1); - return request; - } else { - return _pending_requests.shift(); + this._pending_requests.push({ + config, + data, + method, + promise, + url + }); + return promise; + }; + + private _helperReq = (method: string, url: string, data?: any, config?: any) => { + const conf = data && config ? config : {}; + return this._newReq({ + ...conf, + data, + method, + url, + }); + }; + + private _helperReqNoData = (method: string, url: string, config?: any) => { + return this._helperReq(method, url, {}, config) } -}; -/** - * Removes an item form the queue, based on it's type - * @param queueItem - */ -const popQueueItem = (queueItem: SynchronousPromise | AxiosMockQueueItem = null) => { - // first let's pretend the param is a queue item - const request: AxiosMockQueueItem = MockAxios.popRequest( - queueItem as AxiosMockQueueItem, - ); - - if (request) { - // IF the request was found - // > set the promise - return request.promise; - } else { - // ELSE maybe the `queueItem` is a promise (legacy mode) - return MockAxios.popPromise(queueItem as UnresolvedSynchronousPromise); - } -}; + // mocking Axios methods + get = jest.fn(this._helperReqNoData.bind(this, "get")); + delete = jest.fn(this._helperReqNoData.bind(this, "delete")); + head = jest.fn(this._helperReqNoData.bind(this, "head")); + options = jest.fn(this._helperReqNoData.bind(this, "options")); -MockAxios.mockResponse = ( - response?: HttpResponse, - queueItem: SynchronousPromise | AxiosMockQueueItem = null, - silentMode: boolean = false, -): void => { - // replacing missing data with default values - response = Object.assign( - { - config: {}, - data: {}, - headers: {}, - status: 200, - statusText: "OK", - }, - response, - ); - - const promise = popQueueItem(queueItem); - - if (!promise && !silentMode) { - throw new Error("No request to respond to!"); - } else if (!promise) { - return; - } + post = jest.fn(this._helperReq.bind(this, "post")); + put = jest.fn(this._helperReq.bind(this, "put")); + patch = jest.fn(this._helperReq.bind(this, "patch")); - // resolving the Promise with the given response data - promise.resolve(response); -}; - -MockAxios.mockResponseFor = ( - criteria: string | AxiosMockRequestCriteria, - response?: HttpResponse, - silentMode: boolean = false, -): void => { - if (typeof criteria === "string") { - criteria = {url: criteria}; - } - const queueItem = MockAxios.getReqMatching(criteria); + request = jest.fn(this._newReq); - if (!queueItem && !silentMode) { - throw new Error("No request to respond to!"); - } else if (!queueItem) { - return; - } + interceptors = { + request: { + use: jest.fn(), + eject: jest.fn(), + }, + response: { + use: jest.fn(), + eject: jest.fn(), + }, + }; - MockAxios.mockResponse(response, queueItem, silentMode); -}; + defaults = { + headers: { + common: [], + }, + }; + + popPromise = (promise?: SynchronousPromise) => { + if (promise) { + // remove the promise from pending queue + for (let ix = 0; ix < this._pending_requests.length; ix++) { + const req: AxiosMockQueueItem = this._pending_requests[ix]; + + if (req.promise === promise) { + this._pending_requests.splice(ix, 1); + return req.promise; + } + } + } else { + // take the oldest promise + const req: AxiosMockQueueItem = this._pending_requests.shift(); + return req ? req.promise : void 0; + } + }; -MockAxios.mockError = ( - error: any = {}, - queueItem: SynchronousPromise | AxiosMockQueueItem = null, - silentMode: boolean = false, -) => { - const promise = popQueueItem(queueItem); - - if (!promise && !silentMode) { - throw new Error("No request to respond to!"); - } else if (!promise) { - return; - } + popRequest = (request?: AxiosMockQueueItem) => { + if (request) { + const ix = this._pending_requests.indexOf(request); + if (ix === -1) { + return void 0; + } - // resolving the Promise with the given response data - promise.reject(error); -}; + this._pending_requests.splice(ix, 1); + return request; + } else { + return this._pending_requests.shift(); + } + }; + + /** + * Removes an item form the queue, based on it's type + * @param queueItem + */ + popQueueItem = (queueItem: SynchronousPromise | AxiosMockQueueItem = null) => { + // first let's pretend the param is a queue item + const request: AxiosMockQueueItem = MockAxios.popRequest( + queueItem as AxiosMockQueueItem, + ); + + if (request) { + // IF the request was found + // > set the promise + return request.promise; + } else { + // ELSE maybe the `queueItem` is a promise (legacy mode) + return this.popPromise(queueItem as UnresolvedSynchronousPromise); + } + }; + + mockResponse = ( + response?: HttpResponse, + queueItem: SynchronousPromise | AxiosMockQueueItem = null, + silentMode: boolean = false, + ): void => { + // replacing missing data with default values + response = Object.assign( + { + config: {}, + data: {}, + headers: {}, + status: 200, + statusText: "OK", + }, + response, + ); + + const promise = this.popQueueItem(queueItem); + + if (!promise && !silentMode) { + throw new Error("No request to respond to!"); + } else if (!promise) { + return; + } -MockAxios.lastReqGet = () => { - return _pending_requests[_pending_requests.length - 1]; -}; + // resolving the Promise with the given response data + promise.resolve(response); + }; + + mockResponseFor = ( + criteria: string | AxiosMockRequestCriteria, + response?: HttpResponse, + silentMode: boolean = false, + ): void => { + if (typeof criteria === "string") { + criteria = { url: criteria }; + } + const queueItem = this.getReqMatching(criteria); -MockAxios.lastPromiseGet = () => { - const req = MockAxios.lastReqGet(); - return req ? req.promise : void 0; -}; + if (!queueItem && !silentMode) { + throw new Error("No request to respond to!"); + } else if (!queueItem) { + return; + } -const _findReqByPredicate = (predicate: (item: AxiosMockQueueItem) => boolean) => { - return _pending_requests - .slice() - .reverse() // reverse cloned array to return most recent req - .find(predicate); -} + this.mockResponse(response, queueItem, silentMode); + }; -const _checkCriteria = (item: AxiosMockQueueItem, criteria: AxiosMockRequestCriteria) => { - if (criteria.method !== undefined && criteria.method.toLowerCase() !== item.method.toLowerCase()) { - return false; - } + mockError = ( + error: any = {}, + queueItem: SynchronousPromise | AxiosMockQueueItem = null, + silentMode: boolean = false, + ) => { + const promise = this.popQueueItem(queueItem); - if (criteria.url !== undefined && criteria.url !== item.url) { - return false; - } + if (!promise && !silentMode) { + throw new Error("No request to respond to!"); + } else if (!promise) { + return; + } - return true; -}; + // resolving the Promise with the given response data + promise.reject(error); + }; -MockAxios.getReqMatching = (criteria: AxiosMockRequestCriteria) => { - return _findReqByPredicate((x) => _checkCriteria(x, criteria)); -}; + lastReqGet = () => { + return this._pending_requests[this._pending_requests.length - 1]; + }; -MockAxios.getReqByUrl = (url: string) => { - return MockAxios.getReqMatching({url}); -}; + lastPromiseGet = () => { + const req = this.lastReqGet(); + return req ? req.promise : void 0; + }; + + private _findReqByPredicate = (predicate: (item: AxiosMockQueueItem) => boolean) => { + return this._pending_requests + .slice() + .reverse() // reverse cloned array to return most recent req + .find(predicate); + }; + + getReqMatching = (criteria: AxiosMockRequestCriteria) => { + return this._findReqByPredicate((x) => _checkCriteria(x, criteria)); + }; + + getReqByUrl = (url: string) => { + return this.getReqMatching({ url }); + }; + + getReqByMatchUrl = (url: RegExp) => { + return this._findReqByPredicate((x) => url.test(x.url)); + }; + + queue = () => { + return this._pending_requests; + }; + + reset = () => { + // remove all the requests + this._pending_requests.splice(0, this._pending_requests.length); + + // resets all information stored in the mockFn.mock.calls and mockFn.mock.instances arrays + this.get.mockClear(); + this.post.mockClear(); + this.put.mockClear(); + this.patch.mockClear(); + this.delete.mockClear(); + this.head.mockClear(); + this.options.mockClear(); + this.request.mockClear(); + MockAxios.all.mockClear(); + }; +} -MockAxios.getReqByMatchUrl = (url: RegExp) => { - return _findReqByPredicate((x) => url.test(x.url)); -}; +// @ts-ignore +const MockAxios: AxiosMockType = new MockAxiosInstance(); -MockAxios.queue = () => { - return _pending_requests; -}; +MockAxios.all = jest.fn((values) => Promise.all(values)); +// @ts-ignore +MockAxios.create = jest.fn(() => new MockAxiosInstance()); -MockAxios.reset = () => { - // remove all the requests - _pending_requests.splice(0, _pending_requests.length); - - // resets all information stored in the mockFn.mock.calls and mockFn.mock.instances arrays - MockAxios.get.mockClear(); - MockAxios.post.mockClear(); - MockAxios.put.mockClear(); - MockAxios.patch.mockClear(); - MockAxios.delete.mockClear(); - MockAxios.head.mockClear(); - MockAxios.options.mockClear(); - MockAxios.request.mockClear(); - MockAxios.all.mockClear(); -}; MockAxios.Cancel = Cancel; MockAxios.CancelToken = CancelToken; diff --git a/test/index.spec.ts b/test/index.spec.ts index 175fa3a..f0c0ef6 100644 --- a/test/index.spec.ts +++ b/test/index.spec.ts @@ -1,5 +1,6 @@ import { SynchronousPromise, UnresolvedSynchronousPromise } from "synchronous-promise"; import MockAxios from "../lib/index"; +import { MockAxiosInstance } from "../lib/mock-axios"; describe("MockAxios", () => { afterEach(() => { @@ -40,8 +41,11 @@ describe("MockAxios", () => { const promise = Promise.resolve(""); expect(MockAxios.all([promise])).toBeInstanceOf(Promise); }); - it("`create` should return reference to MockAxios itself", () => { - expect(MockAxios.create()).toBe(MockAxios); + it("`create` should return an instance of MockAxios itself", () => { + expect(MockAxios.create()).toBeInstanceOf(MockAxiosInstance); + }); + it("`create` should return different instances for subsequent calls", () => { + expect(MockAxios.create()).not.toBe(MockAxios.create()); }); });