Răsfoiți Sursa

feat(testing): Create helpers for testing of ErrorResult union types

Relates to #437
Michael Bromley 5 ani în urmă
părinte
comite
6ef6045478

+ 81 - 0
packages/testing/src/error-result-guard.ts

@@ -0,0 +1,81 @@
+declare function fail(error?: any): never;
+
+/**
+ * Convenience method for creating an {@link ErrorResultGuard}. Takes a predicate function which
+ * tests whether the input is considered successful (true) or an error result (false).
+ *
+ * Note that the resulting variable must _still_ be type annotated in order for the TypeScript
+ * type inference to work as expected:
+ *
+ * @example
+ * ```TypeScript
+ * const orderResultGuard: ErrorResultGuard<AddItemToOrderResult>
+ *   = createErrorResultGuard<AddItemToOrderResult>(order => !!order.lines);
+ * ```
+ * @docsCategory testing
+ */
+export function createErrorResultGuard<T>(testFn: (input: T) => boolean): ErrorResultGuard<T> {
+    return new ErrorResultGuard<T>(testFn);
+}
+
+/**
+ * @description
+ * A utility class which is used to assert the success of an operation
+ * which returns a union type of `SuccessType | ErrorResponse [ | ErrorResponse ]`.
+ * The methods of this class are used to:
+ * 1. assert that the result is a success or error case
+ * 2. narrow the type so that TypeScript can correctly infer the properties of the result.
+ *
+ * @example
+ * ```TypeScript
+ * const orderResultGuard: ErrorResultGuard<AddItemToOrderResult>
+ *   = createErrorResultGuard<AddItemToOrderResult>(order => !!order.lines);
+ *
+ * it('errors when quantity is negative', async () => {
+ *    const { addItemToOrder } = await shopClient.query<AddItemToOrder.Query, AddItemToOrder.Mutation>(ADD_ITEM_TO_ORDER, {
+ *      productVariantId: 42, quantity: -1,
+ *    });
+ *
+ *    // The test will fail
+ *    orderResultGuard.assertErrorResult(addItemToOrder);
+ *
+ *    // the type of `addItemToOrder` has now been
+ *    // narrowed to only include the ErrorResult types.
+ *    expect(addItemToOrder.errorCode).toBe(ErrorCode.NegativeQuantityError);
+ * }
+ * ```
+ * @docsCategory testing
+ */
+export class ErrorResultGuard<T> {
+    constructor(private testFn: (input: T) => boolean) {}
+
+    /**
+     * @description
+     * A type guard which returns `true` if the input passes the `testFn` predicate.
+     */
+    isSuccess(input: T | any): input is T {
+        return this.testFn(input as T);
+    }
+
+    /**
+     * @description
+     * Asserts (using the testing library's `fail()` function) that the input is
+     * successful, i.e. it passes the `testFn`.
+     */
+    assertSuccess<R>(input: T | R): asserts input is T {
+        if (!this.isSuccess(input)) {
+            fail(`Unexpected error: ${JSON.stringify(input)}`);
+        }
+    }
+
+    /**
+     * @description
+     * Asserts (using the testing library's `fail()` function) that the input is
+     * not successful, i.e. it does not pass the `testFn`.
+     */
+    assertErrorResult<R>(input: T | R): asserts input is R {
+        if (this.isSuccess(input)) {
+            fail(`Should have errored`);
+        }
+    }
+}

+ 1 - 0
packages/testing/src/index.ts

@@ -4,6 +4,7 @@ export * from './config/test-config';
 export * from './create-test-environment';
 export * from './data-population/clear-all-tables';
 export * from './data-population/populate-customers';
+export * from './error-result-guard';
 export * from './initializers/initializers';
 export * from './initializers/test-db-initializer';
 export * from './initializers/mysql-initializer';