@thisisagile/easy-test
Version:
Straightforward library for testing microservices built with @thisisagile/easy
618 lines (592 loc) • 22.5 kB
JavaScript
;
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