UNPKG

@thisisagile/easy-test

Version:

Straightforward library for testing microservices built with @thisisagile/easy

618 lines (592 loc) 22.5 kB
"use strict"; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var src_exports = {}; __export(src_exports, { Fails: () => Fails, FailsWith: () => FailsWith, Match: () => Match, MatchesExactJson: () => MatchesExactJson, MatchesJson: () => MatchesJson, Mocks: () => Mocks, ObjectContainingJson: () => ObjectContainingJson, ObjectContainingText: () => ObjectContainingText, ObjectContainingTextExact: () => ObjectContainingTextExact, Passes: () => Passes, PassesWith: () => PassesWith, check: () => check, checkDefined: () => checkDefined, fits: () => fits, match: () => match, mock: () => mock, toBeArrayOf: () => toBeArrayOf, toBeArrayOfWithLength: () => toBeArrayOfWithLength, toBeAt: () => toBeAt, toBeBadGateway: () => toBeBadGateway, toBeBadRequest: () => toBeBadRequest, toBeConflict: () => toBeConflict, toBeCreated: () => toBeCreated, toBeExactlyAt: () => toBeExactlyAt, toBeForbidden: () => toBeForbidden, toBeInternalServerError: () => toBeInternalServerError, toBeNotFound: () => toBeNotFound, toBeOk: () => toBeOk, toBeOkWithItems: () => toBeOkWithItems, toBeQueriedWith: () => toBeQueriedWith, toBeRoutedTo: () => toBeRoutedTo, toBeUnauthorized: () => toBeUnauthorized, toBeValid: () => toBeValid, toFail: () => toFail, toFailMatcher: () => toFailMatcher, toFailMatcherWith: () => toFailMatcherWith, toFailWith: () => toFailWith, toHaveNoContent: () => toHaveNoContent, toHaveStatus: () => toHaveStatus, toMatchArray: () => toMatchArray, toMatchAsString: () => toMatchAsString, toMatchExactJson: () => toMatchExactJson, toMatchException: () => toMatchException, toMatchJson: () => toMatchJson, toMatchRoute: () => toMatchRoute, toMatchText: () => toMatchText, toPassMatcher: () => toPassMatcher, toPassMatcherWith: () => toPassMatcherWith, toResultWith: () => toResultWith, weExpectedButReceivedInstead: () => weExpectedButReceivedInstead }); module.exports = __toCommonJS(src_exports); // src/utils/Utils.ts var isDefined = (o) => o !== void 0 && o !== null; var isNumber = (o) => isDefined(o) && typeof o === "number" && !Number.isNaN(o); var isFunction = (o) => isDefined(o) && typeof o === "function"; var isA = (t, ...properties) => isDefined(t) && properties.every((p) => isDefined(t[p])); var isArray = (o) => isDefined(o) && o instanceof Array; var isObject = (o) => o != null && (typeof o === "object" || typeof o === "function") && !isArray(o); var asJson = (a) => a?.toJSON ? a.toJSON() : isObject(a) ? a : void 0; var asString = (a) => a?.toString(); var asNumber = (num, alt) => { const n = parseInt(asString(num)); return isNumber(n) ? n : isFunction(alt) ? alt() : isNumber(alt) ? alt : NaN; }; var toArray = (...items) => items.length > 1 ? items : isArray(items[0]) ? items[0] : isDefined(items[0]) ? [items[0]] : []; // src/utils/Types.ts var toMessage = (g, ...params) => asString(isFunction(g) ? g(...params) : g); // src/matchers/Match.ts var Match = class _Match { constructor(subject, failed = false, message = "") { this.subject = subject; this.failed = failed; this.message = message; } not(p, message) { if (this.failed) { return this; } try { return new _Match(this.subject, !p(this.subject), toMessage(message, this.subject)); } catch (e) { return new _Match(this.subject, true, e.message); } } undefined(p, message) { return this.not(() => isDefined(p(this.subject)), message); } else(message) { return { pass: !this.failed, message: () => this.failed ? toMessage(this.message) : `${toMessage(message, this.subject)}, which we did not expect.` }; } }; var match = (subject) => new Match(subject); // src/matchers/HttpMatchers.ts var toHaveStatus = (res, code) => match(res).undefined((r) => r, "Response is unknown.").undefined( (r) => r?.status?.id, () => "Response does not have a status code" ).not( (r) => r.status.id === code, (r) => `Response does not have code '${code}', but has code '${r.status.id}' instead.` ).else(`Response does have status code '${code}'.`); var toBeOk = (res) => toHaveStatus(res, 200); var toBeCreated = (res) => toHaveStatus(res, 201); var toHaveNoContent = (res) => toHaveStatus(res, 204); var toBeBadRequest = (res) => toHaveStatus(res, 400); var toBeUnauthorized = (res) => toHaveStatus(res, 401); var toBeForbidden = (res) => toHaveStatus(res, 403); var toBeNotFound = (res) => toHaveStatus(res, 404); var toBeConflict = (res) => toHaveStatus(res, 409); var toBeInternalServerError = (res) => toHaveStatus(res, 500); var toBeBadGateway = (res) => toHaveStatus(res, 502); var toBeOkWithItems = (res, length) => match(res).undefined((r) => r.status?.id, "Response did not have a status").not( (r) => r.status.id === 200, (r) => `Response did not have status '200'. It had status '${r.status.id}' instead.` ).undefined((r) => r?.body?.data?.items, `Response did not have any items.`).not( (r) => (r?.body?.data?.itemCount ?? 0) >= length, (r) => `Response did not have at least ${length} items. It only had ${r?.body?.data?.itemCount ?? 0} items.` ).else(`Response had status 200 and at least ${length} items`); expect.extend({ toBeOk, toBeOkWithItems, toBeCreated, toHaveNoContent, toBeNotFound, toBeBadRequest, toBeUnauthorized, toBeForbidden, toBeConflict, toBeInternalServerError, toHaveStatus, toBeBadGateway }); // src/matchers/Check.ts var Check = class _Check { constructor(ctx, received, expected, failed = false, message = "") { this.ctx = ctx; this.received = received; this.expected = expected; this.failed = failed; this.message = message; } print(message) { return (typeof message === "function" ? message([this.received, this.expected]) : message).replace("{r}", this.ctx.utils.printReceived(this.received)).replace("{e}", this.ctx.utils.printExpected(this.expected)).replace("{diff}", this.ctx.utils.diff(this.received, this.expected) ?? ""); } not(p, message) { if (this.failed) { return this; } try { return new _Check(this.ctx, this.received, this.expected, !p([this.received, this.expected]), this.print(message)); } catch (e) { return new _Check(this.ctx, this.received, this.expected, true, e.message); } } undefined(p, message) { return this.not(() => isDefined(p([this.received, this.expected])), this.print(message)); } else(message = "Expected {r} not to match with {e}, but it did.") { return { pass: !this.failed, message: () => this.failed ? this.message : this.print(message) }; } }; var check = (ctx, received, expected) => new Check(ctx, received, expected); var checkDefined = (ctx, received, expected) => new Check(ctx, received, expected).undefined(([r]) => r, "Received array is undefined.").undefined(([, e]) => e, "Expected array is undefined."); // src/matchers/ResultMatchers.ts var notDefined = "Results is not defined."; var doesNotFail = "Results does not fail."; var hasMessage = (res, message) => res.results.some((r) => r.message === message); var messages = (res) => "'" + res?.results.map((r) => r.message).join("', '") + "'"; var toResultWith = (results, message) => match(results).undefined((r) => r, notDefined).not( (r) => hasMessage(r, message), (r) => `Results does not have message '${message}', but it has messages ${messages(r)} instead.` ).else(`Succeeds with message ${message}`); var toFail = (results) => match(results).undefined((r) => r, notDefined).not((r) => !r.isValid, doesNotFail).else("Results does not fail"); var toFailWith = (results, message) => match(results).undefined((r) => r, notDefined).not((r) => !r.isValid, doesNotFail).not( (r) => hasMessage(r, message), (r) => `Fails, but results does not have message '${message}', but it has messages ${messages(r)} instead.` ).else(`Fails with message '${message}'`); expect.extend({ toResultWith, toFail, toFailWith }); // src/matchers/toBeArrayOf.ts var toBeArrayOf = (items, ctor) => match(items).undefined((it) => it, "Subject is undefined.").not((it) => it instanceof Array, "Subject is not an array.").not((it) => it.every((i) => i instanceof ctor), `Not all elements are of type '${ctor.name}'.`).else(`All elements in array are of type '${ctor.name}'`); expect.extend({ toBeArrayOf }); // src/matchers/toBeArrayOfWithLength.ts var toBeArrayOfWithLength = (items, ctor, length) => match(items).undefined((it) => it, "Subject is undefined.").not((it) => it instanceof Array, "Subject is not an array.").not( (it) => it.length === length, (it) => `Subject does not have ${length} elements, but ${it.length}.` ).not((it) => it.every((i) => i instanceof ctor), `Not all elements are of type '${ctor.name}'.`).else(`Subject has ${length} elements, which are all of type '${ctor.name}'`); expect.extend({ toBeArrayOfWithLength }); // src/matchers/toBeAt.ts var toBeAt = (tester, uc, id) => { return match(tester).undefined((t) => t, "Tester is undefined").undefined((t) => t.url, "Tester does not contain a URL").undefined(() => uc, "Use case is undefined").not( (t) => t.url.includes(`/${uc?.app.id}`), (t) => `We expected the tester to be at app '${uc?.app.id}', but it is at '${t?.url}' instead.` ).not( (t) => t.url.includes(`/${uc?.id}`), (t) => `We expected the tester to be at use case '${uc?.id}', but it is at '${t?.url}' instead.` ).not( (t) => t.url.includes(id ? `/${id}` : ""), (t) => `We expected the path to contain '/42', but it is '${t?.url}' instead.` ).else((t) => `The tester is at '${t?.url}'`); }; expect.extend({ toBeAt }); // src/matchers/toBeExactlyAt.ts var toUrl = (uc, id) => { const i = isDefined(id) ? `/${id}` : ""; return `/${uc.app.id}/${uc.id}${i}`; }; var toBeExactlyAt = (tester, uc, id) => { return match(tester).undefined((t) => t, "Tester is undefined").undefined((t) => t.url, "Tester does not contain a URL").undefined(() => uc, "Use case is undefined").not( (t) => t.url.includes(toUrl(uc, id)), (t) => `We expected the tester to be at: '${toUrl(uc, id)}', but it is at: '${t?.url}' instead.` ).else((t) => `The tester is at '${t?.url}'`); }; expect.extend({ toBeExactlyAt }); // src/matchers/toFailMatcher.ts var Fails = { Yes: "Match fails, instead of passes.", No: (reason) => `Match doesn't fail, because '${reason}'` }; var FailsWith = { Yes: "Match fails with correct message.", No: (message, instead) => `Match does fail, however not with message '${message}', but with message '${instead}' instead.` }; var toFailMatcher = (result) => match(result).not( (c) => !c.pass, (c) => Fails.No(c.message()) ).else(Fails.Yes); var toFailMatcherWith = (result, message) => match(result).not( (c) => !c.pass, (c) => Fails.No(c.message()) ).not( (c) => c.message().includes(toMessage(message)), (c) => FailsWith.No(toMessage(message), c.message()) ).else(FailsWith.Yes); expect.extend({ toFailMatcher, toFailMatcherWith }); // src/matchers/toBeValid.ts var toBeValid = (v) => match(v).undefined((s) => s, "Subject is undefined.").not((s) => isA(s, "isValid"), "Subject is not validatable.").not((s) => s.isValid, `Subject is not valid.`).else(`Subject is valid`); expect.extend({ toBeValid }); // src/matchers/toMatchArray.ts function toMatchArray(received, expected) { return checkDefined(this, received, expected).not( ([r, e]) => r.length === e.length, ([r, e]) => `Received array has length ${r.length}, while expected array has length ${e.length}.` ).not(([r, e]) => r.every((el, i) => this.equals(el, e[i])), "Elements in {r} do not match elements in {e}. \n\n {diff}.").else(); } expect.extend({ toMatchArray }); // src/utils/Eq.ts var import_expect_utils = require("@jest/expect-utils"); var eq = { exact: (a, b) => (0, import_expect_utils.equals)(a, b, []), subset: (a, b) => (0, import_expect_utils.equals)(a, b, [import_expect_utils.iterableEquality, import_expect_utils.subsetEquality]), string: (a, b) => asString(a) === asString(b) }; // src/matchers/toMatchExactJson.ts var MatchesExactJson = { SubjectUndefined: "Subject is undefined.", SubsetUndefined: "Object to match with is undefined.", DoesNotMatch: "Object does not exactly match subject.", Yes: "Object matches subject exactly" }; var toMatchExactJson = (value, json) => match(value).undefined((v) => v, MatchesExactJson.SubjectUndefined).undefined(() => json, MatchesExactJson.SubsetUndefined).not((v) => eq.exact(v, json), MatchesExactJson.DoesNotMatch).else(() => MatchesExactJson.Yes); expect.extend({ toMatchExactJson }); // src/matchers/toMatchException.ts var toMatchException = (received, expected, reason) => match(expected).undefined((e) => e.id, "Expected value is not an exception.").not( (e) => e.id === received.id, (e) => `Expected exception has id '${e.id}', while the received exception has id '${received.id}'.` ).not( () => !isDefined(reason) || isDefined(reason) && isDefined(received.reason), () => `We expected to have reason '${reason}', but we received no reason.` ).not( () => !isDefined(reason) || isDefined(reason) && received.reason === reason, () => `We expected to have reason '${reason}', but we received reason '${received.reason}'.` ).else(`Expected exception matches received exception`); expect.extend({ toMatchException }); // src/matchers/toMatchJson.ts var MatchesJson = { SubjectUndefined: "Subject is undefined.", SubsetUndefined: "Subset to match with is undefined.", DoesNotMatch: "Subset does not match subject.", Yes: "Subset matches subject" }; var toMatchJson = (value, subset) => match(value).undefined((v) => v, MatchesJson.SubjectUndefined).undefined(() => subset, MatchesJson.SubsetUndefined).not((v) => eq.subset(asJson(v), asJson(subset)), MatchesJson.DoesNotMatch).else(() => MatchesJson.Yes); expect.extend({ toMatchJson }); // src/matchers/toMatchRoute.ts var toMatchRoute = (uri, route) => match(uri).undefined((u) => u, "Subject is undefined.").undefined(() => route, "Route to include is undefined.").not( (u) => asString(u).includes(asString(route)), (u) => `Uri '${u}' does not include '${route}'.` ).else((u) => `Uri '${u}' includes '${route}'`); expect.extend({ toMatchRoute }); // src/matchers/toMatchText.ts var toMatchText = (value, text) => match(value).undefined((v) => v, "Subject is undefined.").undefined(() => text, "Text to match with is undefined.").not( (v) => asString(v) === asString(text), (v) => `Text '${v}' does not match with text '${text}'.` ).else((v) => `Text '${v}' matches`); expect.extend({ toMatchText }); // src/matchers/toPassMatcher.ts var Passes = { Yes: "Match passes, instead of fails.", No: (reason) => `Match doesn't pass, because '${reason}'` }; var PassesWith = { Yes: "Match passes with correct message.", No: (message, instead) => `Match does pass, however not with message '${message}', but with message '${instead}' instead.` }; var toPassMatcher = (result) => match(result).not( (c) => c.pass, (c) => Passes.No(c.message()) ).else(Passes.Yes); var toPassMatcherWith = (result, message) => match(result).not( (c) => c.pass, (c) => Passes.No(c.message()) ).not( (c) => c.message().includes(toMessage(message)), (c) => PassesWith.No(toMessage(message), c.message()) ).else(PassesWith.Yes); expect.extend({ toPassMatcher, toPassMatcherWith }); // src/matchers/toBeQueriedWith.ts var toBeQueriedWith = (query, expected) => match(query?.mock?.calls).undefined((c) => c, "Query is unknown.").not((c) => c.length === 1, "Query did not execute.").not( (c) => c[0][0].toString() === expected?.toString(), (c) => `We expected query '${expected}', but we received query '${c[0][0]}' instead.` ).else(`Received query does match '${expected}'`); expect.extend({ toBeQueriedWith }); // src/matchers/toBeRoutedTo.ts var weExpectedButReceivedInstead = ([r, e]) => `We expected ${asString(e)}, but we received '${asString(r)}' instead.`; function toMatchAsString(received, expected) { return checkDefined(this, received, expected).not( ([r, e]) => this.equals(asString(r), asString(e)), ([r, e]) => weExpectedButReceivedInstead([r, e]) ).else(); } var toBeRoutedTo = (query, expected) => match(query?.mock?.calls).undefined((c) => c, "Uri is unknown.").not((c) => c.length === 1, "Method was not called.").not( (c) => asString(c[0][0]) === asString(expected), (c) => `We expected uri '${asString(expected)}', but we received uri '${asString(c[0][0])}' instead.` ).else(`Called uri does match '${asString(expected)}'`); expect.extend({ toBeRoutedTo }); // src/mock/Fits.ts var import_expect = require("expect"); var ObjectContainingText = class extends import_expect.AsymmetricMatcher { asymmetricMatch(other) { return asString(other).includes(asString(this.sample)); } toString() { return `String${this.inverse ? "Not" : ""}Containing`; } }; var ObjectContainingTextExact = class extends import_expect.AsymmetricMatcher { asymmetricMatch(other) { return asString(other) === asString(this.sample); } toString() { return `String${this.inverse ? "Not" : ""}Containing`; } }; var ObjectContainingJson = class extends import_expect.AsymmetricMatcher { asymmetricMatch(other) { return eq.subset(asJson(other), asJson(this.sample)); } toString() { return `Object${this.inverse ? "Not" : ""}Containing`; } }; var fits = { any: () => expect.anything(), type: (type) => expect.any(type), with: (o) => expect.objectContaining(o), text: (s) => new ObjectContainingText(s), textExact: (s) => new ObjectContainingTextExact(s), uri: (u) => fits.textExact(u), json: (s) => new ObjectContainingJson(s), items: (...items) => expect.arrayContaining(toArray(...items)) }; // src/utils/Req.ts var Req = class { constructor(state = {}) { this.state = state; this.skip = isDefined(this.query?.skip) ? asNumber(this.query?.skip) : void 0; this.take = isDefined(this.query?.take) ? asNumber(this.query?.take) : void 0; } skip; take; get id() { return this.state.id ?? this.path.id; } get q() { return this.state.q ?? this.query.q; } get path() { return this.state?.path ?? {}; } get query() { return this.state?.query ?? {}; } get body() { return this.state.body; } get headers() { return this.state.headers; } get = (key) => this?.state[key.toString()] ?? this.path[key.toString()] ?? this.query[key.toString()]; }; // src/mock/Mocks.ts var Mocks = class { req = { id: (id) => new Req({ id }), q: (q) => new Req({ q }), with: (a) => new Req(a), body: (body) => new Req({ body }), path: (path) => new Req({ path }), query: (query) => new Req({ query }) }; resp = { items: (status, items = []) => ({ status, body: { data: { code: status.id, itemCount: items.length, items } } }), errors: (status, message, errors = []) => ({ status, body: { error: { code: status.id, message, errorCount: errors.length, errors } } }) }; provider = { data: (...items) => ({ execute: jest.fn().mockResolvedValue({ body: { data: { itemCount: items.length, items } } }) }) }; static getArg = (mock2, call = 0, arg = 0) => { if (!isJestMock(mock2)) throw new Error("Function provided is not a Jest mock"); return mock2.mock.calls[call]?.[arg]; }; clear = () => jest.clearAllMocks(); impl = (f) => jest.fn().mockImplementation(f); property = (object, getter, value) => jest.spyOn(object, getter, "get").mockReturnValue(value); reject = (value) => jest.fn().mockRejectedValue(value); rejectWith = (props = {}) => jest.fn().mockRejectedValue(mock.a(props)); resolve = (value) => jest.fn().mockResolvedValue(value); resolveWith = (props = {}) => jest.fn().mockResolvedValue(mock.a(props)); return = (value) => jest.fn().mockReturnValue(value); returnWith = (props = {}) => jest.fn().mockReturnValue(mock.a(props)); this = () => jest.fn().mockReturnThis(); empty = (props = {}) => props; a = this.empty; an = this.empty; date = (epoch = 1621347575) => { const date = new Date(epoch); date.toString = mock.return("Mon Jan 19 1970 19:22:27 GMT+0100 (Central European Standard Time)"); date.toLocaleDateString = mock.return("19/01/1970"); date.toDateString = mock.return("19/01/1970"); return date; }; once = (...values) => values.reduce((m, v) => m.mockImplementationOnce(() => v), jest.fn()); }; function isJestMock(fn) { return typeof fn === "function" && "mock" in fn && Array.isArray(fn.mock?.calls); } var mock = new Mocks(); // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { Fails, FailsWith, Match, MatchesExactJson, MatchesJson, Mocks, ObjectContainingJson, ObjectContainingText, ObjectContainingTextExact, Passes, PassesWith, check, checkDefined, fits, match, mock, toBeArrayOf, toBeArrayOfWithLength, toBeAt, toBeBadGateway, toBeBadRequest, toBeConflict, toBeCreated, toBeExactlyAt, toBeForbidden, toBeInternalServerError, toBeNotFound, toBeOk, toBeOkWithItems, toBeQueriedWith, toBeRoutedTo, toBeUnauthorized, toBeValid, toFail, toFailMatcher, toFailMatcherWith, toFailWith, toHaveNoContent, toHaveStatus, toMatchArray, toMatchAsString, toMatchExactJson, toMatchException, toMatchJson, toMatchRoute, toMatchText, toPassMatcher, toPassMatcherWith, toResultWith, weExpectedButReceivedInstead }); //# sourceMappingURL=index.js.map