@jaypie/testkit 0.1.2 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -13,6 +13,7 @@
13
13
  "oidc",
14
14
  "pinia",
15
15
  "roboto",
16
+ "sorpresa",
16
17
  "testkit",
17
18
  "unplugin",
18
19
  "vendia",
package/README.md CHANGED
@@ -12,16 +12,212 @@ npm install --save-dev @jaypie/testkit
12
12
 
13
13
  ### Example
14
14
 
15
- TODO: Example should include one trivial and possibly one thorough example of using the library
15
+ #### Log Spying
16
+
17
+ ```javascript
18
+ import { restoreLog, spyLog } from "@jaypie/testkit";
19
+ import { log } from "@jaypie/core";
20
+
21
+ beforeEach(() => {
22
+ spyLog(log);
23
+ });
24
+ afterEach(() => {
25
+ restoreLog(log);
26
+ vi.clearAllMocks();
27
+ });
28
+
29
+ test("log", () => {
30
+ log.warn("Danger");
31
+ expect(log.warn).toHaveBeenCalled();
32
+ expect(log.error).not.toHaveBeenCalled();
33
+ });
34
+ ```
35
+
36
+ 👺 Logging Conventions:
37
+
38
+ * Only use `log.trace` or `log.var` during "happy path"
39
+ * Use `log.debug` for edge cases
40
+ * Now you can add an "observability" test that will fail as soon as new code triggers an unexpected edge condition
41
+
42
+ ```javascript
43
+ describe("Observability", () => {
44
+ it("Does not log above trace", async () => {
45
+ // Arrange
46
+ // TODO: "happy path" setup
47
+ // Act
48
+ await myNewFunction(); // TODO: add any "happy path" parameters
49
+ // Assert
50
+ expect(log.debug).not.toHaveBeenCalled();
51
+ expect(log.info).not.toHaveBeenCalled();
52
+ expect(log.warn).not.toHaveBeenCalled();
53
+ expect(log.error).not.toHaveBeenCalled();
54
+ expect(log.fatal).not.toHaveBeenCalled();
55
+ });
56
+ });
57
+ ```
58
+
59
+ > 👺 Follow the "arrange, act, assert" pattern
60
+
61
+ #### Test Matchers
62
+
63
+ testSetup.js
64
+
65
+ ```javascript
66
+ import { matchers as jaypieMatchers } from "@jaypie/testkit";
67
+ import * as extendedMatchers from "jest-extended";
68
+ import { expect } from "vitest";
69
+
70
+ expect.extend(extendedMatchers);
71
+ expect.extend(jaypieMatchers);
72
+ ```
73
+
74
+ test.spec.js
75
+
76
+ ```javascript
77
+ import { ConfigurationError } from "@jaypie/core";
78
+
79
+ const error = new ConfigurationError();
80
+ const json = error.json();
81
+ expect(error).toBeJaypieError();
82
+ expect(json).toBeJaypieError();
83
+ ```
16
84
 
17
85
  ## 📖 Reference
18
86
 
19
- TODO: Reference should be a complete list of everything in the package
87
+ ```
88
+ import {
89
+ jsonApiErrorSchema,
90
+ jsonApiSchema,
91
+ matchers,
92
+ mockLogFactory,
93
+ restoreLog,
94
+ spyLog,
95
+ } from '@jaypie/testkit'
96
+ ```
97
+
98
+ ### `jsonApiErrorSchema`
99
+
100
+ A [JSON Schema](https://json-schema.org/) validator for the [JSON:API](https://jsonapi.org/) error schema. Powers the `toBeJaypieError` matcher (via `toMatchSchema`).
101
+
102
+ ### `jsonApiSchema`
103
+
104
+ A [JSON Schema](https://json-schema.org/) validator for the [JSON:API](https://jsonapi.org/) data schema.
105
+
106
+ ### `matchers`
107
+
108
+ testSetup.js
109
+
110
+ ```javascript
111
+ import { matchers as jaypieMatchers } from "@jaypie/testkit";
112
+ import * as extendedMatchers from "jest-extended";
113
+ import { expect } from "vitest";
114
+
115
+ expect.extend(extendedMatchers);
116
+ expect.extend(jaypieMatchers);
117
+ ```
118
+
119
+ #### `expect(subject).toBeJaypieError()`
120
+
121
+ Validates instance objects:
122
+
123
+ ```javascript
124
+ try {
125
+ throw new Error("Sorpresa!");
126
+ } catch (error) {
127
+ expect(error).not.toBeJaypieError();
128
+ }
129
+ ```
130
+
131
+ Validates plain old JSON:
132
+
133
+ ```javascript
134
+ expect({ errors: [ { status, title, detail } ] }).toBeJaypieError();
135
+ ```
136
+
137
+ Jaypie errors, which are `ProjectErrors`, all have a `.json()` to convert
138
+
139
+ #### `expect(subject).toBeValidSchema()`
140
+
141
+ ```javascript
142
+ import { jsonApiErrorSchema, jsonApiSchema } from "@jaypie/testkit";
143
+
144
+ expect(jsonApiErrorSchema).toBeValidSchema();
145
+ expect(jsonApiSchema).toBeValidSchema();
146
+ expect({ project: "mayhem" }).not.toBeValidSchema();
147
+ ```
148
+
149
+ From `jest-json-schema` [toBeValidSchema.js](https://github.com/americanexpress/jest-json-schema/blob/main/matchers/toBeValidSchema.js) (not documented in README)
150
+
151
+ #### `expect(subject).toMatchSchema(schema)`
152
+
153
+ ```javascript
154
+ import { jsonApiErrorSchema, jsonApiSchema } from "@jaypie/testkit";
155
+ import { ConfigurationError } from "@jaypie/core";
156
+
157
+ const error = new ConfigurationError();
158
+ const json = error.json();
159
+ expect(json).toMatchSchema(jsonApiErrorSchema);
160
+ expect(json).not.toMatchSchema(jsonApiSchema);
161
+ ```
162
+
163
+ From `jest-json-schema`; see [README](https://github.com/americanexpress/jest-json-schema?tab=readme-ov-file#tomatchschemaschema)
164
+
165
+ ### `mockLogFactory()`
166
+
167
+ Creates a mock of the `log` provided by `@jaypie/core`.
168
+
169
+ ```javascript
170
+ import { mockLogFactory } from "@jaypie/testkit";
171
+
172
+ const log = mockLogFactory();
173
+ log.warn("Danger");
174
+ expect(log.warn).toHaveBeenCalled();
175
+ expect(log.error).not.toHaveBeenCalled();
176
+ ```
177
+
178
+ ### `restoreLog(log)`
179
+
180
+ Restores the `log` provided by `@jaypie/core`, commonly performed `afterEach` with `spyLog` in `beforeEach`. See example with `spyLog`.
181
+
182
+ ### `spyLog(log)`
183
+
184
+ Spies on the `log` provided by `@jaypie/core`, commonly performed `beforeEach` with `restoreLog` in `afterEach`.
185
+
186
+ ```javascript
187
+ import { restoreLog, spyLog } from "@jaypie/testkit";
188
+ import { log } from "@jaypie/core";
189
+
190
+ beforeEach(() => {
191
+ spyLog(log);
192
+ });
193
+ afterEach(() => {
194
+ restoreLog(log);
195
+ vi.clearAllMocks();
196
+ });
197
+
198
+ test("log", () => {
199
+ log.warn("Danger");
200
+ expect(log.warn).toHaveBeenCalled();
201
+ expect(log.error).not.toHaveBeenCalled();
202
+ });
203
+ ```
204
+
205
+ ## 🌠 Wishlist
206
+
207
+ * matcher toBeHttpStatus
208
+ * matcher toBeJaypieAny
209
+ * matcher toBeJaypieData
210
+ * matcher toBeJaypieDataObject
211
+ * matcher toBeJaypieDataArray
212
+ * matcher toThrowJaypieError
213
+ * ...@knowdev/jest
20
214
 
21
215
  ## 📝 Changelog
22
216
 
23
217
  | Date | Version | Summary |
24
218
  | ---------- | ------- | -------------- |
219
+ | 3/16/2024 | 1.0.0 | Artists ship |
220
+ | 3/15/2024 | 0.1.0 | Initial deploy |
25
221
  | 3/15/2024 | 0.0.1 | Initial commit |
26
222
 
27
223
  ## 📜 License
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jaypie/testkit",
3
- "version": "0.1.2",
3
+ "version": "1.0.0",
4
4
  "author": "Finlayson Studio",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -15,12 +15,14 @@
15
15
  "test:spec:index": "vitest run ./src/__tests__/index.spec.js",
16
16
  "test:spec:jsonApiSchema.module": "vitest run ./src/__tests__/jsonApiSchema.module.spec.js",
17
17
  "test:spec:matchers.module": "vitest run ./src/__tests__/matchers.module.spec.js",
18
- "test:spec:mockLog.module": "vitest run ./src/__tests__/mockLog.module.spec.js"
18
+ "test:spec:mockLog.module": "vitest run ./src/__tests__/mockLog.module.spec.js",
19
+ "test:spec:toBeJaypieError.matcher": "vitest run ./src/matchers/__tests__/toBeJaypieError.matcher.spec.js"
19
20
  },
20
21
  "dependencies": {
21
22
  "jest-json-schema": "^6.1.0"
22
23
  },
23
24
  "devDependencies": {
25
+ "@jaypie/core": "^0.3.7",
24
26
  "eslint": "^8.57.0",
25
27
  "eslint-config-prettier": "^9.1.0",
26
28
  "eslint-plugin-import": "^2.29.1",
@@ -12,6 +12,7 @@ export const jsonApiErrorSchema = {
12
12
  },
13
13
  required: ["status", "title"],
14
14
  },
15
+ minItems: 1,
15
16
  },
16
17
  },
17
18
  required: ["errors"],
@@ -0,0 +1,100 @@
1
+ // eslint-disable-next-line no-unused-vars
2
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
3
+
4
+ import { ConfigurationError } from "@jaypie/core";
5
+
6
+ // Subject
7
+ import toBeJaypieError from "../toBeJaypieError.matcher.js";
8
+
9
+ //
10
+ //
11
+ // Mock constants
12
+ //
13
+
14
+ //
15
+ //
16
+ // Mock modules
17
+ //
18
+
19
+ //
20
+ //
21
+ // Mock environment
22
+ //
23
+
24
+ const DEFAULT_ENV = process.env;
25
+ beforeEach(() => {
26
+ process.env = { ...process.env };
27
+ });
28
+ afterEach(() => {
29
+ process.env = DEFAULT_ENV;
30
+ });
31
+
32
+ //
33
+ //
34
+ // Run tests
35
+ //
36
+
37
+ describe("To Be Jaypie Error Matcher", () => {
38
+ it("Is a function", () => {
39
+ expect(toBeJaypieError).toBeFunction();
40
+ });
41
+ describe("Error Conditions", () => {
42
+ it("Rejects instances of plain error objects", () => {
43
+ const error = new Error(
44
+ "If this is your first night at Fight Club, you have to fight",
45
+ );
46
+ const result = toBeJaypieError(error);
47
+ expect(result.message).toBeFunction();
48
+ expect(result.message()).toBeString();
49
+ expect(result.pass).toBeFalse();
50
+ });
51
+ it("Rejects if nothing passed", () => {
52
+ const result = toBeJaypieError();
53
+ expect(result.message).toBeFunction();
54
+ expect(result.message()).toBeString();
55
+ expect(result.pass).toBeFalse();
56
+ });
57
+ it("Rejects if non-object passed", () => {
58
+ const result = toBeJaypieError(12);
59
+ expect(result.message).toBeFunction();
60
+ expect(result.message()).toBeString();
61
+ expect(result.pass).toBeFalse();
62
+ });
63
+ it("Rejects if no errors array", () => {
64
+ const result = toBeJaypieError({});
65
+ expect(result.message).toBeFunction();
66
+ expect(result.message()).toBeString();
67
+ expect(result.pass).toBeFalse();
68
+ });
69
+ it("Rejects if errors array is empty", () => {
70
+ const result = toBeJaypieError({ errors: [] });
71
+ expect(result.message).toBeFunction();
72
+ expect(result.message()).toBeString();
73
+ expect(result.pass).toBeFalse();
74
+ });
75
+ it("Must match the entire json:api error schema", () => {
76
+ const result = toBeJaypieError({ errors: ["taco"] });
77
+ expect(result.message).toBeFunction();
78
+ expect(result.message()).toBeString();
79
+ expect(result.pass).toBeFalse();
80
+ });
81
+ });
82
+ describe("Happy Path", () => {
83
+ it("Matches instances of Jaypie error objects", () => {
84
+ const error = new ConfigurationError();
85
+ const result = toBeJaypieError(error);
86
+ expect(result.message).toBeFunction();
87
+ expect(result.message()).toBeString();
88
+ expect(result.pass).toBeTrue();
89
+ });
90
+ it("Matches plain old json errors", () => {
91
+ const error = new ConfigurationError(
92
+ "If this is your first night at Fight Club, you have to fight",
93
+ ).json();
94
+ const result = toBeJaypieError(error);
95
+ expect(result.message).toBeFunction();
96
+ expect(result.message()).toBeString();
97
+ expect(result.pass).toBeTrue();
98
+ });
99
+ });
100
+ });
@@ -0,0 +1,57 @@
1
+ import { matchers as jsonSchemaMatchers } from "jest-json-schema";
2
+ import { jsonApiErrorSchema } from "../jsonApiSchema.module.js";
3
+
4
+ //
5
+ //
6
+ // Constants
7
+ //
8
+
9
+ //
10
+ //
11
+ // Helper Functions
12
+ //
13
+
14
+ function isErrorObjectJaypieError(error) {
15
+ if (error.isProjectError) {
16
+ return {
17
+ message: () => `expected "${error}" not to be a Jaypie error`,
18
+ pass: true,
19
+ };
20
+ }
21
+ return {
22
+ message: () => `expected "${error}" to be a Jaypie error`,
23
+ pass: false,
24
+ };
25
+ }
26
+
27
+ //
28
+ //
29
+ // Main
30
+ //
31
+
32
+ const toBeJaypieError = (received) => {
33
+ // See if it is an instance of error:
34
+ if (received instanceof Error) {
35
+ return isErrorObjectJaypieError(received);
36
+ }
37
+
38
+ const result = jsonSchemaMatchers.toMatchSchema(received, jsonApiErrorSchema);
39
+ if (result.pass) {
40
+ return {
41
+ message: () => `expected ${received} not to be a Jaypie error`,
42
+ pass: true,
43
+ };
44
+ } else {
45
+ return {
46
+ message: () => `expected ${received} to be a Jaypie error`,
47
+ pass: false,
48
+ };
49
+ }
50
+ };
51
+
52
+ //
53
+ //
54
+ // Export
55
+ //
56
+
57
+ export default toBeJaypieError;
@@ -1,11 +1,14 @@
1
1
  import { matchers as jsonSchemaMatchers } from "jest-json-schema";
2
2
 
3
+ import toBeJaypieError from "./matchers/toBeJaypieError.matcher.js";
4
+
3
5
  //
4
6
  //
5
7
  // Export
6
8
  //
7
9
 
8
10
  export default {
11
+ toBeJaypieError,
9
12
  toBeValidSchema: jsonSchemaMatchers.toBeValidSchema,
10
13
  toMatchSchema: jsonSchemaMatchers.toMatchSchema,
11
14
  };