diff --git a/README.md b/README.md index 20f6671..867bf34 100644 --- a/README.md +++ b/README.md @@ -226,9 +226,11 @@ mock .onPost( "/product", { id: 1 }, - expect.objectContaining({ - Authorization: expect.stringMatching(/^Basic /), - }) + { + headers: expect.objectContaining({ + Authorization: expect.stringMatching(/^Basic /), + }) + } ) .reply(204); ``` diff --git a/src/handle_request.js b/src/handle_request.js index 82e1924..397d156 100644 --- a/src/handle_request.js +++ b/src/handle_request.js @@ -76,7 +76,7 @@ function handleRequest(mockAdapter, resolve, reject, config) { config.data, config.params, (config.headers && config.headers.constructor.name === 'AxiosHeaders') - ? Object.assign({}, config.headers) + ? Object.assign({}, config.headers.toJSON()) : config.headers, config.baseURL ); diff --git a/src/index.js b/src/index.js index a70e714..cde8c28 100644 --- a/src/index.js +++ b/src/index.js @@ -79,15 +79,46 @@ MockAdapter.prototype.reset = reset; MockAdapter.prototype.resetHandlers = resetHandlers; MockAdapter.prototype.resetHistory = resetHistory; +var methodsWithConfigsAsSecondArg = ["any", "get", "delete", "head", "options"]; +function convertDataAndConfigToConfig (method, data, config) { + if (methodsWithConfigsAsSecondArg.includes(method)) { + return validateconfig(method, data || {}); + } else { + return validateconfig(method, Object.assign({}, config, { data: data })); + } +} + +var allowedConfigProperties = ['headers', 'params', 'data']; +function validateconfig (method, config) { + for (var key in config) { + if (!allowedConfigProperties.includes(key)) { + throw new Error( + 'Invalid config property ' + + JSON.stringify(key) + + ' provided to ' + + toMethodName(method) + + '. Config: ' + + JSON.stringify(config) + ); + } + } + + return config; +} + +function toMethodName (method) { + return "on" + method.charAt(0).toUpperCase() + method.slice(1); +} + VERBS.concat("any").forEach(function (method) { - var methodName = "on" + method.charAt(0).toUpperCase() + method.slice(1); - MockAdapter.prototype[methodName] = function (matcher, body, requestHeaders) { + MockAdapter.prototype[toMethodName(method)] = function (matcher, data, config) { var _this = this; var matcher = matcher === undefined ? /.*/ : matcher; var delay; + var paramsAndBody = convertDataAndConfigToConfig(method, data, config); function reply(code, response, headers) { - var handler = [matcher, body, requestHeaders, code, response, headers, false, delay]; + var handler = [matcher, paramsAndBody, paramsAndBody.headers, code, response, headers, false, delay]; addHandler(method, _this.handlers, handler); return _this; } @@ -100,7 +131,7 @@ VERBS.concat("any").forEach(function (method) { } function replyOnce(code, response, headers) { - var handler = [matcher, body, requestHeaders, code, response, headers, true, delay]; + var handler = [matcher, paramsAndBody, paramsAndBody.headers, code, response, headers, true, delay]; addHandler(method, _this.handlers, handler); return _this; } @@ -113,7 +144,7 @@ VERBS.concat("any").forEach(function (method) { withDelayInMs: withDelayInMs, passThrough: function passThrough() { - var handler = [matcher, body]; + var handler = [matcher, paramsAndBody]; addHandler(method, _this.handlers, handler); return _this; }, diff --git a/src/utils.js b/src/utils.js index 431d4ea..ba06240 100644 --- a/src/utils.js +++ b/src/utils.js @@ -48,20 +48,18 @@ function findHandler( baseURL ) { return find(handlers[method.toLowerCase()], function (handler) { + var matchesUrl = false; if (typeof handler[0] === "string") { - return ( - (isUrlMatching(url, handler[0]) || - isUrlMatching(combineUrls(baseURL, url), handler[0])) && - isBodyOrParametersMatching(method, body, parameters, handler[1]) && - isObjectMatching(headers, handler[2]) - ); + matchesUrl = isUrlMatching(url, handler[0]) || + isUrlMatching(combineUrls(baseURL, url), handler[0]); } else if (handler[0] instanceof RegExp) { - return ( - (handler[0].test(url) || handler[0].test(combineUrls(baseURL, url))) && - isBodyOrParametersMatching(method, body, parameters, handler[1]) && - isObjectMatching(headers, handler[2]) - ); + matchesUrl = handler[0].test(url) || + handler[0].test(combineUrls(baseURL, url)); } + + return matchesUrl && + isBodyOrParametersMatching(body, parameters, handler[1]) && + isObjectMatching(headers, handler[2]); }); } @@ -71,15 +69,9 @@ function isUrlMatching(url, required) { return noSlashUrl === noSlashRequired; } -function isBodyOrParametersMatching(method, body, parameters, required) { - var allowedParamsMethods = ["delete", "get", "head", "options"]; - if (allowedParamsMethods.indexOf(method.toLowerCase()) >= 0) { - var data = required ? required.data : undefined; - var params = required ? required.params : undefined; - return isObjectMatching(parameters, params) && isBodyMatching(body, data); - } else { - return isBodyMatching(body, required); - } +function isBodyOrParametersMatching(body, parameters, required) { + return isObjectMatching(parameters, required && required.params) && + isBodyMatching(body, required && required.data); } function isObjectMatching(actual, expected) { @@ -174,7 +166,12 @@ function createAxiosError(message, config, response, code) { function createCouldNotFindMockError(config) { var message = "Could not find mock for: \n" + - JSON.stringify(config, ["method", "url"], 2); + JSON.stringify({ + method: config.method, + url: config.url, + params: config.params, + headers: config.headers + }, null, 2); var error = new Error(message); error.isCouldNotFindMockError = true; error.url = config.url; diff --git a/test/basics.spec.js b/test/basics.spec.js index 316d574..f68c316 100644 --- a/test/basics.spec.js +++ b/test/basics.spec.js @@ -141,7 +141,7 @@ describe("MockAdapter basics", function () { .reply(200); return instance - .get("/withParams", { params: { bar: "foo", foo: "bar" }, in: true }) + .get("/withParams", { params: { bar: "foo", foo: "bar" } }) .then(function (response) { expect(response.status).to.equal(200); }); @@ -153,7 +153,7 @@ describe("MockAdapter basics", function () { .reply(200); return instance - .delete("/withParams", { params: { bar: "foo", foo: "bar" }, in: true }) + .delete("/withParams", { params: { bar: "foo", foo: "bar" } }) .then(function (response) { expect(response.status).to.equal(200); }); @@ -177,33 +177,33 @@ describe("MockAdapter basics", function () { .reply(200); return instance - .head("/withParams", { params: { bar: "foo", foo: "bar" }, in: true }) + .head("/withParams", { params: { bar: "foo", foo: "bar" } }) .then(function (response) { expect(response.status).to.equal(200); }); }); - it("can't pass query params for post to match to a handler", function () { + it("can pass query params for post to match to a handler", function () { mock - .onPost("/withParams", { params: { foo: "bar", bar: "foo" } }) + .onPost("/withParams", undefined, { params: { foo: "bar", bar: "foo" } }) .reply(200); return instance - .post("/withParams", { params: { foo: "bar", bar: "foo" }, in: true }) - .catch(function (error) { - expect(error.response.status).to.equal(404); + .post("/withParams", { some: 'body' }, { params: { foo: "bar", bar: "foo" } }) + .then(function (response) { + expect(response.status).to.equal(200); }); }); - it("can't pass query params for put to match to a handler", function () { + it("can pass query params for put to match to a handler", function () { mock - .onPut("/withParams", { params: { foo: "bar", bar: "foo" } }) + .onPut("/withParams", undefined, { params: { foo: "bar", bar: "foo" } }) .reply(200); return instance - .put("/withParams", { params: { bar: "foo", foo: "bar" }, in: true }) - .catch(function (error) { - expect(error.response.status).to.equal(404); + .put("/withParams", { some: 'body' }, { params: { bar: "foo", foo: "bar" } }) + .then(function (response) { + expect(response.status).to.equal(200); }); }); @@ -221,7 +221,7 @@ describe("MockAdapter basics", function () { }); }); - it("does not match when parameters are wrong", function () { + it("does not match when params are wrong", function () { mock .onGet("/withParams", { params: { foo: "bar", bar: "foo" } }) .reply(200); @@ -232,7 +232,7 @@ describe("MockAdapter basics", function () { }); }); - it("does not match when parameters are missing", function () { + it("does not match when params are missing", function () { mock .onGet("/withParams", { params: { foo: "bar", bar: "foo" } }) .reply(200); @@ -241,7 +241,7 @@ describe("MockAdapter basics", function () { }); }); - it("matches when parameters were not expected", function () { + it("matches when params were not expected", function () { mock.onGet("/withParams").reply(200); return instance .get("/withParams", { params: { foo: "bar", bar: "foo" } }) @@ -251,18 +251,18 @@ describe("MockAdapter basics", function () { }); it("can pass a body to match to a handler", function () { - mock.onPost("/withBody", { body: { is: "passed" }, in: true }).reply(200); + mock.onPost("/withBody", { somecontent: { is: "passed" } }).reply(200); return instance - .post("/withBody", { body: { is: "passed" }, in: true }) + .post("/withBody", { somecontent: { is: "passed" } }) .then(function (response) { expect(response.status).to.equal(200); }); }); it("does not match when body is wrong", function () { - var body = { body: { is: "passed" }, in: true }; - mock.onPatch("/wrongObjBody", body).reply(200); + var matcher = { somecontent: { is: "passed" } }; + mock.onPatch("/wrongObjBody", matcher).reply(200); return instance .patch("/wrongObjBody", { wrong: "body" }) @@ -294,18 +294,24 @@ describe("MockAdapter basics", function () { "Header-test": "test-header", }; - mock.onPost("/withHeaders", undefined, headers).reply(200); + mock.onPost("/withHeaders", undefined, { headers: headers }).reply(200); return instance .post("/withHeaders", undefined, { headers: headers }) .then(function (response) { expect(response.status).to.equal(200); + + return instance + .post("/withHeaders", undefined, { headers: { Accept: 'no-match' } }) + .catch(function (err) { + expect(err.response.status).to.equal(404); + }); }); }); it("does not match when request header is wrong", function () { var headers = { "Header-test": "test-header" }; - mock.onPatch("/wrongObjHeader", undefined, headers).reply(200); + mock.onPatch("/wrongObjHeader", undefined, { headers: headers }).reply(200); return instance .patch("/wrongObjHeader", undefined, { @@ -558,7 +564,7 @@ describe("MockAdapter basics", function () { it("allows delay in millsecond per request (legacy non-chaining)", function () { mock = new MockAdapter(instance); - var start = new Date().getTime(); + var start = Date.now(); var firstDelay = 100; var secondDelay = 500; var success = 200; @@ -570,14 +576,14 @@ describe("MockAdapter basics", function () { return Promise.all([ instance.get("/foo").then(function (response) { - var end = new Date().getTime(); + var end = Date.now(); var totalTime = end - start; expect(response.status).to.equal(success); expect(totalTime).greaterThanOrEqual(firstDelay); }), instance.get("/bar").then(function (response) { - var end = new Date().getTime(); + var end = Date.now(); var totalTime = end - start; expect(response.status).to.equal(success); @@ -588,7 +594,7 @@ describe("MockAdapter basics", function () { it("allows delay in millsecond per request", function () { mock = new MockAdapter(instance); - var start = new Date().getTime(); + var start = Date.now(); var firstDelay = 100; var secondDelay = 500; var success = 200; @@ -603,14 +609,14 @@ describe("MockAdapter basics", function () { return Promise.all([ instance.get("/foo").then(function (response) { - var end = new Date().getTime(); + var end = Date.now(); var totalTime = end - start; expect(response.status).to.equal(success); expect(totalTime).greaterThanOrEqual(firstDelay); }), instance.get("/bar").then(function (response) { - var end = new Date().getTime(); + var end = Date.now(); var totalTime = end - start; expect(response.status).to.equal(success); @@ -620,7 +626,7 @@ describe("MockAdapter basics", function () { }); it("overrides global delay if request per delay is provided and respects global delay if otherwise", function () { - var start = new Date().getTime(); + var start = Date.now(); var requestDelay = 100; var globalDelay = 500; var success = 200; @@ -632,7 +638,7 @@ describe("MockAdapter basics", function () { return Promise.all([ instance.get("/foo").then(function (response) { - var end = new Date().getTime(); + var end = Date.now(); var totalTime = end - start; expect(response.status).to.equal(success); @@ -641,7 +647,7 @@ describe("MockAdapter basics", function () { expect(totalTime).lessThan(globalDelay); }), instance.get("/bar").then(function (response) { - var end = new Date().getTime(); + var end = Date.now(); var totalTime = end - start; expect(response.status).to.equal(success); @@ -850,7 +856,7 @@ describe("MockAdapter basics", function () { }); }); - it("allows overwriting mocks with parameters", function () { + it("allows overwriting mocks with params", function () { mock .onGet("/users", { params: { searchText: "John" } }) .reply(500) diff --git a/test/on_any.spec.js b/test/on_any.spec.js index a91b194..f063624 100644 --- a/test/on_any.spec.js +++ b/test/on_any.spec.js @@ -45,7 +45,7 @@ describe("MockAdapter onAny", function () { { object: { with: { deep: "property" } }, array: ["1", "abc"] }, "a", ]; - mock.onAny("/anyWithBody", body).reply(200); + mock.onAny("/anyWithBody", { data: body }).reply(200); return instance .put("/anyWithBody", body) @@ -54,6 +54,14 @@ describe("MockAdapter onAny", function () { }) .then(function (response) { expect(response.status).to.equal(200); + + return instance.post("/anyWithBody") + .then(function () { + throw new Error("should not get here"); + }) + .catch(function (err) { + expect(err.response.status).to.equal(404); + }); }); }); diff --git a/test/utils.spec.js b/test/utils.spec.js index c15378e..62fbdd7 100644 --- a/test/utils.spec.js +++ b/test/utils.spec.js @@ -85,17 +85,17 @@ describe("utility functions", function () { context("isBodyOrParametersMatching", function() { it('delete has params only', function () { - expect(isBodyOrParametersMatching('delete', null, { 'a': 2 }, { 'params': { 'a': 2 } } )).to.be.true; - expect(isBodyOrParametersMatching('delete', null, { 'a': 2 }, { 'params': { 'b': 2 } } )).to.be.false; + expect(isBodyOrParametersMatching(null, { 'a': 2 }, { 'params': { 'a': 2 } } )).to.be.true; + expect(isBodyOrParametersMatching(null, { 'a': 2 }, { 'params': { 'b': 2 } } )).to.be.false; }); it('delete has data only', function () { - expect(isBodyOrParametersMatching('delete', { 'x': 1 }, null, { 'data': { 'x': 1 } })).to.be.true; - expect(isBodyOrParametersMatching('delete', { 'x': 1 }, null, { 'data': { 'y': 1 } })).to.be.false; + expect(isBodyOrParametersMatching({ 'x': 1 }, null, { 'data': { 'x': 1 } })).to.be.true; + expect(isBodyOrParametersMatching({ 'x': 1 }, null, { 'data': { 'y': 1 } })).to.be.false; }); it('delete has body and params', function () { - expect(isBodyOrParametersMatching('delete', { 'x': 1 }, { 'a': 2 }, { 'data': { 'x': 1 }, 'params': { 'a': 2 } })).to.be.true; - expect(isBodyOrParametersMatching('delete', { 'x': 1 }, { 'a': 2 }, { 'data': { 'x': 1 }, 'params': { 'b': 2 } })).to.be.false; - expect(isBodyOrParametersMatching('delete', { 'x': 1 }, { 'a': 2 }, { 'data': { 'y': 1 }, 'params': { 'a': 2 } })).to.be.false; + expect(isBodyOrParametersMatching({ 'x': 1 }, { 'a': 2 }, { 'data': { 'x': 1 }, 'params': { 'a': 2 } })).to.be.true; + expect(isBodyOrParametersMatching({ 'x': 1 }, { 'a': 2 }, { 'data': { 'x': 1 }, 'params': { 'b': 2 } })).to.be.false; + expect(isBodyOrParametersMatching({ 'x': 1 }, { 'a': 2 }, { 'data': { 'y': 1 }, 'params': { 'a': 2 } })).to.be.false; }); }); }); diff --git a/types/index.d.ts b/types/index.d.ts index a5afa6b..54a0967 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -33,25 +33,34 @@ interface AsymmetricMatcher { asymmetricMatch: Function; } -interface RequestDataMatcher { - [index: string]: any; - params?: { - [index: string]: any; - }; +interface ParamsMatcher { + [param: string]: any; } interface HeadersMatcher { [header: string]: string; } +type UrlMatcher = string | RegExp; +type AsymmetricParamsMatcher = AsymmetricMatcher | ParamsMatcher; type AsymmetricHeadersMatcher = AsymmetricMatcher | HeadersMatcher; +type AsymmetricRequestDataMatcher = AsymmetricMatcher | any; -type AsymmetricRequestDataMatcher = AsymmetricMatcher | RequestDataMatcher; +interface ConfigMatcher { + params?: AsymmetricParamsMatcher; + headers?: AsymmetricHeadersMatcher; + data?: AsymmetricRequestDataMatcher; +} type RequestMatcherFunc = ( - matcher?: string | RegExp, - body?: string | AsymmetricRequestDataMatcher, - headers?: AsymmetricHeadersMatcher + matcher?: UrlMatcher, + body?: AsymmetricRequestDataMatcher, + config?: ConfigMatcher +) => MockAdapter.RequestHandler; + +type NoBodyRequestMatcherFunc = ( + matcher?: UrlMatcher, + config?: ConfigMatcher ) => MockAdapter.RequestHandler; declare class MockAdapter { @@ -67,15 +76,15 @@ declare class MockAdapter { history: { [method: string]: AxiosRequestConfig[] }; - onGet: RequestMatcherFunc; + onAny: NoBodyRequestMatcherFunc; + onGet: NoBodyRequestMatcherFunc; + onDelete: NoBodyRequestMatcherFunc; + onHead: NoBodyRequestMatcherFunc; + onOptions: NoBodyRequestMatcherFunc; onPost: RequestMatcherFunc; onPut: RequestMatcherFunc; - onHead: RequestMatcherFunc; - onDelete: RequestMatcherFunc; onPatch: RequestMatcherFunc; onList: RequestMatcherFunc; - onOptions: RequestMatcherFunc; - onAny: RequestMatcherFunc; onLink: RequestMatcherFunc; onUnlink: RequestMatcherFunc; } diff --git a/types/test.ts b/types/test.ts index e17a164..da439b6 100644 --- a/types/test.ts +++ b/types/test.ts @@ -75,18 +75,14 @@ namespace AllowsStringBodyMatcher { } namespace AllowsBodyMatcher { - mock.onGet('/foo', { - id: 4, - name: 'foo' - }); + mock.onPost('/foo', {id: 4, name: 'foo'}); + mock.onPut('/foo', {id: 4, name: 'foo'}); + mock.onAny('/foo', {data: {id: 4, name: 'foo'}}); } -namespace AllowsParameterMatcher { - mock.onGet('/foo', { - params: { - searchText: 'John' - } - }); +namespace AllowsParamsMatcher { + mock.onGet('/foo', {params: {searchText: 'John'}}); + mock.onDelete('/foo', {params: {searchText: 'John'}}); } namespace AllowsReplyWithStatus {