diff --git a/src/handle_request.js b/src/handle_request.js index ac6a8f7..3521dc0 100644 --- a/src/handle_request.js +++ b/src/handle_request.js @@ -28,14 +28,14 @@ function handleRequest(mockAdapter, resolve, reject, config) { ); if (handler) { - utils.purgeIfReplyOnce(mockAdapter, handler); - - if (handler.length === 2) { - // passThrough handler + if (handler.length === 2) { // passThrough handler // tell axios to use the original adapter instead of our mock, fixes #35 config.adapter = mockAdapter.originalAdapter; mockAdapter.axiosInstance.request(config).then(resolve, reject); } else if (!(handler[3] instanceof Function)) { + if (handler.length === 7) { + utils.purgeIfReplyOnce(mockAdapter, handler); + } utils.settle( resolve, reject, diff --git a/src/index.js b/src/index.js index a907416..33bd327 100644 --- a/src/index.js +++ b/src/index.js @@ -1,5 +1,7 @@ 'use strict'; +var deepEqual = require('deep-equal'); + var handleRequest = require('./handle_request'); var VERBS = ['get', 'post', 'head', 'delete', 'patch', 'put', 'options']; @@ -24,7 +26,6 @@ function reset() { accumulator[verb] = []; return accumulator; }, {}); - this.replyOnceHandlers = []; } function MockAdapter(axiosInstance, options) { @@ -64,9 +65,8 @@ VERBS.concat('any').forEach(function(method) { reply: reply, replyOnce: function replyOnce(code, response, headers) { - var handler = [matcher, body, requestHeaders, code, response, headers]; + var handler = [matcher, body, requestHeaders, code, response, headers, true]; addHandler(method, _this.handlers, handler); - _this.replyOnceHandlers.push(handler); return _this; }, @@ -96,13 +96,38 @@ VERBS.concat('any').forEach(function(method) { }; }); +function findInHandlers(method, handlers, handler) { + var index = -1; + for (var i = 0; i < handlers[method].length; i += 1) { + var item = handlers[method][i]; + var isReplyOnce = item.length === 7; + var comparePaths = item[0] instanceof RegExp && handler[0] instanceof RegExp + ? String(item[0]) === String(handler[0]) + : item[0] === handler[0]; + var isSame = ( + comparePaths && + deepEqual(item[1], handler[1], { strict: true }) && + deepEqual(item[2], handler[2], { strict: true }) + ); + if (isSame && !isReplyOnce) { + index = i; + } + } + return index; +} + function addHandler(method, handlers, handler) { if (method === 'any') { VERBS.forEach(function(verb) { handlers[verb].push(handler); }); } else { - handlers[method].push(handler); + var indexOfExistingHandler = findInHandlers(method, handlers, handler); + if (indexOfExistingHandler > -1 && handler.length < 7) { + handlers[method].splice(indexOfExistingHandler, 1, handler); + } else { + handlers[method].push(handler); + } } } diff --git a/src/utils.js b/src/utils.js index b97af01..5420cc1 100644 --- a/src/utils.js +++ b/src/utils.js @@ -75,17 +75,12 @@ function isBodyMatching(body, requiredBody) { } function purgeIfReplyOnce(mock, handler) { - var index = mock.replyOnceHandlers.indexOf(handler); - if (index > -1) { - mock.replyOnceHandlers.splice(index, 1); - - Object.keys(mock.handlers).forEach(function(key) { - index = mock.handlers[key].indexOf(handler); - if (index > -1) { - mock.handlers[key].splice(index, 1); - } - }); - } + Object.keys(mock.handlers).forEach(function(key) { + var index = mock.handlers[key].indexOf(handler); + if (index > -1) { + mock.handlers[key].splice(index, 1); + } + }); } function settle(resolve, reject, response, delay) { diff --git a/test/basics.spec.js b/test/basics.spec.js index f0e1dad..06d5fad 100644 --- a/test/basics.spec.js +++ b/test/basics.spec.js @@ -532,4 +532,143 @@ describe('MockAdapter basics', function() { expect(data[0].bar).to.equal(123); }); }); + + it('should overwrite existing mock', function() { + mock.onGet('/').reply(500); + mock.onGet('/').reply(200); + + return instance + .get('/') + .then(function(response) { + expect(response.status).to.equal(200); + }); + }); + + it('should not add duplicate handlers', function() { + mock.onGet('/').replyOnce(312); + mock.onGet('/').reply(200); + mock.onGet('/1').reply(200); + mock.onGet('/2').reply(200); + mock.onGet('/3').replyOnce(300); + mock.onGet('/3').reply(200); + mock.onGet('/4').reply(200); + + expect(mock.handlers['get'].length).to.equal(7); + }); + + it('supports chaining on same path with different params', function() { + mock + .onGet('/users', { params: { searchText: 'John' } }).reply(200, { id: 1 }) + .onGet('/users', { params: { searchText: 'James' } }).reply(200, { id: 2 }) + .onGet('/users', { params: { searchText: 'Jake' } }).reply(200, { id: 3 }) + .onGet('/users', { params: { searchText: 'Jackie' } }).reply(200, { id: 4 }); + + return instance.get('/users', { params: { searchText: 'John' } }) + .then(function(response) { + expect(response.data.id).to.equal(1); + return instance.get('/users', { params: { searchText: 'James' } }); + }) + .then(function(response) { + expect(response.data.id).to.equal(2); + return instance.get('/users', { params: { searchText: 'Jake' } }); + }) + .then(function(response) { + expect(response.data.id).to.equal(3); + return instance.get('/users', { params: { searchText: 'Jackie' } }); + }) + .then(function(response) { + expect(response.data.id).to.equal(4); + }); + }); + + it('should overwrite replys', function() { + mock.onGet('/').reply(500); + mock.onGet('/').reply(200); + mock.onGet('/').reply(401); + + return instance.get('/') + .catch(function(error) { + expect(mock.handlers['get'].length).to.equal(1); + expect(error.response.status).to.equal(401); + }); + }); + + it('should overwrite replys using RegEx', function() { + mock.onGet(/foo\/bar/).reply(500); + mock.onGet(/foo\/bar/).reply(200); + mock.onGet(/foo\/baz\/.+/).reply(200); + + return instance.get('/foo/bar') + .then(function(response) { + expect(mock.handlers['get'].length).to.equal(2); + expect(response.status).to.equal(200); + return instance.get('/foo/baz/56'); + }) + .then(function(response) { + expect(response.status).to.equal(200); + }); + }); + + it('should allow overwriting only on reply if replyOnce was used first', function() { + var counter = 0; + mock.onGet('/').replyOnce(500); + mock.onGet('/').reply(200); + mock.onGet('/').reply(401); + + return instance.get('/') + .catch(function(error) { + expect(error.response.status).to.equal(500); + counter += 1; + return instance.get('/'); + }) + .catch(function(error) { + expect(error.response.status).to.equal(401); + counter += 1; + }) + .then(function() { + expect(counter).to.equal(2); + }); + }); + + it('should not allow overwriting only on reply if replyOnce wasn\'t used first', function() { + var counter = 0; + mock.onGet('/').reply(200); + mock.onGet('/').reply(401); + mock.onGet('/').replyOnce(500); + mock.onGet('/').reply(500); + + return instance.get('/') + .catch(function(error) { + expect(error.response.status).to.equal(500); + counter += 1; + return instance.get('/'); + }) + .catch(function(error) { + expect(error.response.status).to.equal(500); + counter += 1; + }) + .then(function() { + expect(counter).to.equal(2); + }); + }); + it('allows overwriting mocks with parameters', function() { + mock + .onGet('/users', { params: { searchText: 'John' } }) + .reply(500) + .onGet('/users', { params: { searchText: 'John' } }) + .reply(200, { id: 1 }); + + return instance.get('/users', { params: { searchText: 'John' } }) + .then(function(response) { + expect(response.status).to.equal(200); + }); + }); + + it.only('allows overwriting mocks with headers', function() { + mock.onGet('/', {}, { 'Accept-Charset': 'utf-8' }).reply(500); + mock.onGet('/', {}, { 'Accept-Charset': 'utf-8' }).reply(200); + + expect(mock.handlers['get'].length).to.equal(1); + expect(mock.handlers['get'][0][3]).to.equal(200); + }); });