Jelajahi Sumber

docs(testing): Create documentation for testing package

Michael Bromley 6 tahun lalu
induk
melakukan
3c76aad483

+ 6 - 0
docs/assets/styles/_markdown.scss

@@ -96,6 +96,12 @@ $block-border-radius: 4px;
         color: $color-code-text;
         border-radius: $block-border-radius;
         border: 1px solid $color-code-border;
+
+
+    }
+
+    a > code:not([data-lang]) {
+        color: $color-link;
     }
 
     pre:not(.chroma) {

+ 101 - 0
docs/content/docs/developer-guide/testing.md

@@ -0,0 +1,101 @@
+---
+title: "Testing"
+showtoc: true
+---
+
+# Testing
+
+Vendure plugins allow you to extend all aspects of the standard Vendure server. When a plugin gets somewhat complex (defining new entities, extending the GraphQL schema, implementing custom resolvers), you may wish to create automated tests to ensure your plugin is correct.
+
+The `@vendure/testing` package gives you some simple but powerful tooling for creating end-to-end tests for your custom Vendure code.
+
+## Usage
+
+### Install dependencies
+
+* [`@vendure/testing`](https://www.npmjs.com/package/@vendure/testing)
+* [`jest`](https://www.npmjs.com/package/jest) You'll need to install a testing framework. In this example we will use [Jest](https://jestjs.io/), but any other framework such as Jasmine should work too.
+* [`graphql-tag`](https://www.npmjs.com/package/graphql-tag) This is not strictly required but makes it much easier to create the DocumentNodes needed to query your server.
+
+Please see the [Jest documentation](https://jestjs.io/docs/en/getting-started) on how to get set up. The remainder of this article will assume a working Jest setup configured to work with TypeScript.
+
+### Create a test environment
+
+The `@vendure/testing` package exports a [`createTestEnvironment` function]({{< relref "create-test-environment" >}}) which is used to set up a Vendure server and GraphQL clients to interact with both the Shop and Admin APIs:
+
+```TypeScript
+import { createTestEnvironment, testConfig } from '@vendure/testing';
+import { MyPlugin } from '../my-plugin.ts';
+
+describe('my plugin', () => {
+
+    const { server, adminClient, shopClient } = createTestEnvironment({
+        ...testConfig,
+        plugins: [MyPlugin],
+    });
+
+});
+```
+
+Notice that we pass a [`VendureConfig`]({{< relref "vendure-config" >}}) object into the `createTestEnvironment` function. The testing package provides a special [`testConfig`]({{< relref "test-config" >}}) which is pre-configured for e2e tests, but any aspect can be overridden for your tests. Here we are configuring the server to load the plugin under test, `MyPlugin`. 
+
+{{% alert "warning" %}}
+**Note**: If you need to deeply merge in some custom configuration, use the [`mergeConfig` function]({{< relref "merge-config" >}}) which is provided by `@vendure/core`.
+{{% /alert %}}
+
+### Initialize the server
+
+The [`TestServer`]({{< relref "test-server" >}}) needs to be initialized before it can be used. The `TestServer.init()` method takes an options object which defines how to populate the server:
+
+```TypeScript
+import { myInitialData } from './fixtures/my-initial-data.ts';
+
+// ...
+
+beforeAll(async () => {
+    await server.init({
+        productsCsvPath: path.join(__dirname, 'fixtures/e2e-products.csv'),
+        initialData: myInitialData,
+        dataDir: path.join(__dirname, '__data__'),
+        customerCount: 2,
+    });
+    await adminClient.asSuperAdmin();
+}, 60000);
+
+afterAll(async () => {
+    await server.destroy();
+});
+```
+
+An explanation of the options:
+
+* `productsCsvPath` This is a path to a CSV file containing product data. The format is as-yet undocumented and may be subject to change, but you can see [an example used in the Vendure e2e tests](https://github.com/vendure-ecommerce/vendure/blob/master/packages/core/e2e/fixtures/e2e-products-full.csv) to get an idea of how it works. To start with you can just copy this file directly and use it as-is.
+* `initialData` This is an object which defines how other non-product data (Collections, ShippingMethods, Countries etc.) is populated. Again, the best idea is to [copy this example from the Vendure e2e tests](https://github.com/vendure-ecommerce/vendure/blob/master/packages/core/e2e/fixtures/e2e-initial-data.ts)
+* `dataDir` The first time this test suite is run, the data populated by the options above will be saved into an SQLite file, stored in the directory specified by this options. On subsequent runs of the test suite, the data-population step will be skipped and the data directly loaded from the SQLite file. This method of caching significantly speeds up the e2e test runs. All the .sqlite files created in the `dataDir` can safely be deleted at any time.
+* `customerCount` Specifies the number of fake Customers to create. Defaults to 10 if not specified.
+
+### Write your tests
+
+Now we are all set up to create a test. Let's test one of the GraphQL queries used by our fictional plugin:
+
+```TypeScript
+import gql from 'graphql-tag';
+
+it('myNewQuery returns the expected result', async () => {
+    adminClient.asSuperAdmin(); // log in as the SuperAdmin user
+
+    const query = gql`
+        query MyNewQuery($id: ID!) {
+            myNewQuery(id: $id) {
+                field1
+                field2
+            }
+        }
+    `;
+    const result = await adminClient.query(query, { id: 123 });
+
+    expect(result.myNewQuery).toEqual({ /* ... */ })
+});
+```
+
+Running the test will then assert that your new query works as expected.

+ 0 - 1
packages/core/src/config/default-config.ts

@@ -23,7 +23,6 @@ import { RuntimeVendureConfig } from './vendure-config';
  * The default configuration settings which are used if not explicitly overridden in the bootstrap() call.
  *
  * @docsCategory configuration
- * @docsPage Configuration
  */
 export const defaultConfig: RuntimeVendureConfig = {
     channelTokenKey: 'vendure-token',

+ 15 - 3
packages/core/src/config/merge-config.ts

@@ -4,9 +4,21 @@ import { simpleDeepClone } from '@vendure/common/lib/simple-deep-clone';
 import { PartialVendureConfig, VendureConfig } from './vendure-config';
 
 /**
- * Deep merge config objects. Based on the solution from https://stackoverflow.com/a/34749873/772859
- * but modified so that it does not overwrite fields of class instances, rather it overwrites
- * the entire instance.
+ * @description
+ * Performs a deep merge of two VendureConfig objects. Unlike `Object.assign()` the `target` object is
+ * not mutated, instead the function returns a new object which is the result of deeply merging the
+ * values of `source` into `target`.
+ *
+ * @example
+ * ```TypeScript
+ * const result = mergeConfig(defaultConfig, {
+ *   assetOptions: {
+ *     uploadMaxFileSize: 5000,
+ *   },
+ * };
+ * ```
+ *
+ * @docsCategory configuration
  */
 export function mergeConfig<T extends VendureConfig>(target: T, source: PartialVendureConfig, depth = 0): T {
     if (!source) {

+ 0 - 2
packages/core/src/config/vendure-config.ts

@@ -373,7 +373,6 @@ export interface WorkerOptions {
  * [`VendureConfig`](https://github.com/vendure-ecommerce/vendure/blob/master/server/src/config/vendure-config.ts) interface.
  *
  * @docsCategory configuration
- * @docsPage Configuration
  * */
 export interface VendureConfig {
     /**
@@ -531,7 +530,6 @@ export interface VendureConfig {
  * config values have been merged with the {@link defaultConfig} values.
  *
  * @docsCategory configuration
- * @docsPage Configuration
  */
 export interface RuntimeVendureConfig extends Required<VendureConfig> {
     assetOptions: Required<AssetOptions>;

+ 11 - 1
packages/testing/src/config/test-config.ts

@@ -10,7 +10,17 @@ import { TestingAssetStorageStrategy } from './testing-asset-storage-strategy';
 import { TestingEntityIdStrategy } from './testing-entity-id-strategy';
 
 /**
- * Config settings used for e2e tests
+ * @description
+ * A {@link VendureConfig} object used for e2e tests. This configuration uses sqljs as the database
+ * and configures some special settings which are optimized for e2e tests:
+ *
+ * * `entityIdStrategy: new TestingEntityIdStrategy()` This ID strategy uses auto-increment IDs but encodes all IDs
+ * to be prepended with the string `'T_'`, so ID `1` becomes `'T_1'`.
+ * * `logger: new NoopLogger()` Do no output logs by default
+ * * `assetStorageStrategy: new TestingAssetStorageStrategy()` This strategy does not actually persist any binary data to disk.
+ * * `assetPreviewStrategy: new TestingAssetPreviewStrategy()` This strategy is a no-op.
+ *
+ * @docsCategory testing
  */
 export const testConfig: Required<VendureConfig> = mergeConfig(defaultConfig, {
     port: 3050,

+ 40 - 0
packages/testing/src/create-test-environment.ts

@@ -3,7 +3,18 @@ import { VendureConfig } from '@vendure/core';
 import { SimpleGraphQLClient } from './simple-graphql-client';
 import { TestServer } from './test-server';
 
+/**
+ * @description
+ * The return value of {@link createTestEnvironment}, containing the test server
+ * and clients for the Shop API and Admin API.
+ *
+ * @docsCategory testing
+ */
 export interface TestEnvironment {
+    /**
+     * @description
+     * A Vendure server instance against which GraphQL requests can be made.
+     */
     server: TestServer;
     /**
      * @description
@@ -17,6 +28,35 @@ export interface TestEnvironment {
     shopClient: SimpleGraphQLClient;
 }
 
+/**
+ * @description
+ * Configures a {@link TestServer} and a {@link SimpleGraphQLClient} for each of the GraphQL APIs
+ * for use in end-to-end tests. Returns a {@link TestEnvironment} object.
+ *
+ * @example
+ * ```TypeScript
+ * import { createTestEnvironment, testConfig } from '\@vendure/testing';
+ *
+ * describe('some feature to test', () => {
+ *
+ *   const { server, adminClient, shopClient } = createTestEnvironment(testConfig);
+ *
+ *   beforeAll(async () => {
+ *     await server.init({
+ *         // ... server options
+ *     });
+ *     await adminClient.asSuperAdmin();
+ *   });
+ *
+ *   afterAll(async () => {
+ *       await server.destroy();
+ *   });
+ *
+ *   // ... end-to-end tests here
+ * });
+ * ```
+ * @docsCategory testing
+ */
 export function createTestEnvironment(config: Required<VendureConfig>): TestEnvironment {
     const server = new TestServer(config);
     const adminClient = new SimpleGraphQLClient(

+ 30 - 0
packages/testing/src/simple-graphql-client.ts

@@ -27,7 +27,10 @@ export type QueryParams = { [key: string]: string | number };
 
 // tslint:disable:no-console
 /**
+ * @description
  * A minimalistic GraphQL client for populating and querying test data.
+ *
+ * @docsCategory testing
  */
 export class SimpleGraphQLClient {
     private authToken: string;
@@ -36,20 +39,30 @@ export class SimpleGraphQLClient {
 
     constructor(private vendureConfig: Required<VendureConfig>, private apiUrl: string = '') {}
 
+    /**
+     * @description
+     * Sets the authToken to be used in each GraphQL request.
+     */
     setAuthToken(token: string) {
         this.authToken = token;
         this.headers.Authorization = `Bearer ${this.authToken}`;
     }
 
+    /**
+     * @description
+     * Returns the authToken currently being used.
+     */
     getAuthToken(): string {
         return this.authToken;
     }
 
+    /** @internal */
     setChannelToken(token: string) {
         this.headers[this.vendureConfig.channelTokenKey] = token;
     }
 
     /**
+     * @description
      * Performs both query and mutation operations.
      */
     async query<T = any, V = Record<string, any>>(
@@ -71,11 +84,19 @@ export class SimpleGraphQLClient {
         }
     }
 
+    /**
+     * @description
+     * Performs a query or mutation and returns the resulting status code.
+     */
     async queryStatus<T = any, V = Record<string, any>>(query: DocumentNode, variables?: V): Promise<number> {
         const response = await this.request(query, variables);
         return response.status;
     }
 
+    /**
+     * @description
+     * Attemps to log in with the specified credentials.
+     */
     async asUserWithCredentials(username: string, password: string) {
         // first log out as the current user
         if (this.authToken) {
@@ -91,10 +112,18 @@ export class SimpleGraphQLClient {
         return result.login;
     }
 
+    /**
+     * @description
+     * Logs in as the SuperAdmin user.
+     */
     async asSuperAdmin() {
         await this.asUserWithCredentials(SUPER_ADMIN_USER_IDENTIFIER, SUPER_ADMIN_USER_PASSWORD);
     }
 
+    /**
+     * @description
+     * Logs out so that the client is then treated as an anonymous user.
+     */
     async asAnonymousUser() {
         await this.query(
             gql`
@@ -140,6 +169,7 @@ export class SimpleGraphQLClient {
     }
 
     /**
+     * @description
      * Uses curl to post a multipart/form-data request to the server. Due to differences between the Node and browser
      * environments, we cannot just use an existing library like apollo-upload-client.
      *

+ 11 - 5
packages/testing/src/test-server.ts

@@ -11,17 +11,21 @@ import { Mutable, TestServerOptions } from './types';
 
 // tslint:disable:no-console
 /**
- * A server against which the e2e tests should be run.
+ * @description
+ * A real Vendure server against which the e2e tests should be run.
+ *
+ * @docsCategory testing
  */
 export class TestServer {
-    app: INestApplication;
-    worker?: INestMicroservice;
+    private app: INestApplication;
+    private worker?: INestMicroservice;
 
     constructor(private vendureConfig: Required<VendureConfig>) {}
 
     /**
+     * @description
      * Bootstraps an instance of Vendure server and populates the database according to the options
-     * passed in. Should be called immediately after creating the client in the `beforeAll` function.
+     * passed in. Should be called in the `beforeAll` function.
      *
      * The populated data is saved into an .sqlite file for each test file. On subsequent runs, this file
      * is loaded so that the populate step can be skipped, which speeds up the tests significantly.
@@ -51,7 +55,9 @@ export class TestServer {
     }
 
     /**
-     * Destroy the Vendure instance. Should be called in the `afterAll` function.
+     * @description
+     * Destroy the Vendure server instance and clean up all resources.
+     * Should be called after all tests have run, e.g. in an `afterAll` function.
      */
     async destroy() {
         // allow a grace period of any outstanding async tasks to complete

+ 31 - 0
packages/testing/src/types.ts

@@ -5,10 +5,41 @@ import { InitialData } from '@vendure/core';
  */
 export type Mutable<T> = { -readonly [K in keyof T]: T[K] };
 
+/**
+ * @description
+ * Configuration options used to initialize an instance of the {@link TestServer}.
+ *
+ * @docsCategory testing
+ */
 export interface TestServerOptions {
+    /**
+     * @description
+     * The directory in which the populated SQLite database files will be
+     * saved. These files are a cache to speed up subsequent runs of e2e tests.
+     */
     dataDir: string;
+    /**
+     * @description
+     * The path to a CSV file containing product data to import.
+     */
     productsCsvPath: string;
+    /**
+     * @description
+     * An object containing non-product data which is used to populate the database.
+     */
     initialData: InitialData;
+    /**
+     * @description
+     * The number of fake Customers to populate into the database.
+     *
+     * @default 10
+     */
     customerCount?: number;
+    /**
+     * @description
+     * Set this to `true` to log some information about the database population process.
+     *
+     * @default false
+     */
     logging?: boolean;
 }

+ 1 - 0
scripts/docs/generate-typescript-docs.ts

@@ -23,6 +23,7 @@ const sections: DocsSectionConfig[] = [
             'packages/asset-server-plugin/src/',
             'packages/email-plugin/src/',
             'packages/elasticsearch-plugin/src/',
+            'packages/testing/src/',
         ],
         exclude: [
             /generated-shop-types/,