UNPKG

pact-gen-ts

Version:

Generating pact files from typescript definitions

396 lines (315 loc) 11.8 kB
# Pact-gen-ts Pact-gen-ts is a tool for generating contracts using TypeScript type definitions and custom JSDoc tags. It's an alternative to the [pact-js](https://github.com/pact-foundation/pact-js) package but without the necessity for writing separate tests. It provides automated, low maintenance and more flexible way to generate contracts according to [Pact specification version 2](https://github.com/pact-foundation/pact-specification). ## Installation and usage You can install pact-gen-ts using npm: ```bash npm install pact-gen-ts --save-dev ``` or yarn: ```bash yarn add --dev pact-gen-ts ``` Next you should create a minimal `pacts.config.js` or `pacts.config.cjs` configuration file in the root directory: ```js module.exports = { consumer: 'consumer-name', providers: [ { provider: 'some-provider', files: ['src/api/**/*.ts'], }, ], }; ``` where `files` property will be an array of glob patterns pointing to API functions definitions. After that pact-gen-ts is ready, now you need to mark all API functions which will be analysed: ```ts /** * @pact */ function fetchComments() { // ... } ``` The last thing is to execute the command: ```bash pact-gen-ts ``` which does the analysis and generates pacts in JSON format inside (by default) `./pacts` directory. ## Compatibility with TypeScript Due to TypeScript's occasional changes to its compiler API and not following semantic versioning in their releases, the latest versions of pact-gen-ts can only guarantee compatibility with the latest versions of TypeScript. If you're limited to historical versions of TypeScript, you should install a corresponding version of pact-gen-ts. The below table presents what TS versions pact-gen-ts will work with: | pact-gen-ts | TypeScript | | --------------- | ---------- | | 0.8 | 4.1 - 4.2 | | 0.9 - 0.9.3 | 4.5 - 4.6 | | 0.9.4 - 0.10.0 | 4.7 - 4.8 | | 0.11.0 | 4.9 | | 0.12.0 | 5.0 | | 0.13.0 | 5.1 | | 0.14.0 | 5.2 - 5.3 | | 0.15.0 | 5.4 | | 0.16.0 | 5.5 - 5.6 | | 0.17.0 - 0.19.0 | \>=5.7 | ## Configuration Pact-gen-ts uses configuration stored in `pacts.config.js` file in project's root directory: ```js module.exports = { consumer: 'consumer-name', buildDir: 'pacts', verbose: true, providers: [ { provider: 'provider-name', files: ['src/api/firstProvider/*.ts'], queryArrayFormat: 'indices', requestHeaders: { authorization: 'auth', }, responseHeaders: { 'Content-Type': 'application/json', }, }, ], }; ``` ### Options | Option | Required | Default | Description | | ------------------------------ | :------: | :----------: | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `consumer` | Yes | - | Consumer's name | | `providers[].provider` | Yes | - | Provider's name | | `providers[].files` | Yes | - | Array of glob patterns where API functions are defined | | `providers[].requestHeaders` | No | - | Request headers shared across all requests | | `providers[].responseHeaders` | No | - | Response headers shared across all responses | | `providers[].queryArrayFormat` | No | `"brackets"` | Sets separator for array in query - possible options are `"indices"`, `"brackets"`, `"comma"` and `"repeat"` [(source)](https://github.com/ljharb/qs#stringifying). The default value is `brackets`. | | `buildDir` | No | `./pacts` | Directory where generated pacts will be placed | | `verbose` | No | `false` | If set to `true` additional information during pacts generating process will be logged | You can specify common config shared between providers in **pacts.config.js**: ```js module.exports = { commonConfigForProviders: { queryArrayFormat: 'indices', requestHeaders: { authorization: 'auth', }, responseHeaders: { 'Content-Type': 'application/json', }, }, providers: [ { provider: 'first-provider', files: ['src/api1/**/*.ts'], }, { provider: 'second-provider', files: ['src/api2/**/*.ts'], }, { provider: 'third-provider', files: ['src/api3/**/*.ts'], // you can override common config in provider config queryArrayFormat: 'comma', }, ], }; ``` ## Integrations #### Axios - `@pact-axios` Sets REST method, expected body for the current response, expected body for current request and query based on axios definitions. ```ts /** * @pact * @pact-axios * @pact-path /api */ async function fetchComments(commentId: string) { const {data} = await axios.post<string>('/api', {commentId}); // ... } ``` **IMPORTANT** - If axios function does not return any type explicitly it is needed to set `<void>` as an axios return type ```ts /** * @pact * @pact-axios * @pact-path /api */ async function fetchComments(commentId: string) { await axios.post<void>('/api', {commentId}); } ``` ## Pact interaction options These JSDoc custom tags are used to adjust generated pact interactions. #### `@pact-method` Sets REST method (GET, POST, PUT, PATCH, DELETE etc.). ```ts /** * @pact * @pact-method GET */ function fetchComments() { // ... } ``` #### `@pact-path` Sets path. ```ts /** * @pact * @pact-path /api/images/100 */ function fetchImage(imageId: number) { // ... } ``` #### `@pact-description` Sets description, if not provided, description is set using name of the function / variable / property. ```ts /** * @pact * @pact-description "request to get comments" */ function fetchComments() { // ... } ``` #### `@pact-response-status` Sets response status, if not provided, it is set based on given HTTP method. ```ts /** * @pact * @pact-response-status 200 */ function fetchComments() { // ... } ``` #### `@pact-request-header` Adds a header to the current request, can override option defined in `pacts.config.js`. ```ts /** * @pact * @pact-request-header "Content-Type" "application/pdf" */ function fetchImage(imageId: number) { // ... } ``` #### `@pact-response-header` Adds a header to the current response, can override option defined in `pacts.config.js`. ```ts /** * @pact * @pact-response-header "Content-Type" "application/pdf" */ function fetchImage(imageId: number) { // ... } ``` #### `@pact-response-body` Sets expected body for the current response. ```ts /** * @pact */ async function fetchComments() { // ... const response = await axios.get<string>('/api'); /** @pact-response-body */ const data = response.data; // ... } ``` **IMPORTANT** - JSDoc has to be applied to separate variable - **not** directly to axios response ```ts async function fetchComments() { // ... /** @pact-response-body */ -WRONG!; const response = await axios.get<string>('/api'); /** @pact-response-body */ -CORRECT; const data = response.data; // ... } ``` #### `@pact-request-body` Sets expected body for current request. ```ts function addComment(/** @pact-request-body */ newComment: NewComment) { // ... } interface NewComment { content: string; postId: string; } ``` or ```js function addComment(postId: string, commentContent: string) { /** @pact-request-body */ const newComment = { postId, commentContent, }; // ... } ``` #### `@pact-query` Sets query, **IMPORTANT** - JSDoc tag has to be applied to an object - not a primitive value. Array separator format can be set using `queryArrayFormat` in providers options. ```ts function fetchComments(/** @pact-query */ query: Query) { // ... } interface Query { fromUser: string; postId: string; } ``` or ```ts function fetchComments(pageNo: string) { /** @pact-query */ const params = { pageNo, }; // ... } ``` ### Pact matchers Typescript types can describe the shape of the data and define possible values a variable can store. Pacts definition require specific values, that's why for some individual cases additional information needs to be added. For example a type `string` without any modifications will be replaced with simple `text` which can be later matched by type. Sometimes that's not enough - the matcher needs to be more specific, for instance instead of simple `text` we need a string in a particular format like `name@example.com` - that's where a `@pact-matcher` tag is used. Pact-matchers are used in the type/interface definition: ```ts interface CommentDTO { id: number; /** @pact-matcher email */ user: string; } ``` Provided common matchers: | Pact matcher | Result | | ----------------------------------------------- | ----------------------------------- | | `/** @pact-matcher email */` | email@example.com | | `/** @pact-matcher iso-date */` | 2021-04-13 | | `/** @pact-matcher iso-datetime */` | 2021-04-13T10:14:53+01:00 | | `/** @pact-matcher iso-datetime-with-millis */` | 2021-04-13T10:14:53.123+01:00 | | `/** @pact-matcher iso-time */` | T10.14.53.342Z | | `/** @pact-matcher timestamp */` | Tue, 13 Apr 2021 10:14:53 -0400 | | `/** @pact-matcher uuid */` | ce11b6e-d8e1-11e7-9296-cec278b6b50a | | `/** @pact-matcher ipv4 */` | 127.0.0.13 | | `/** @pact-matcher ipv6 */` | ::ffff:192.0.2.128 | | `/** @pact-matcher hex */` | A4C3Ff | If that's not enough you can easily provide own value using `/** @pact-example */`: ```ts interface Address { city: string; address: string; /** @pact-example 99-400 */ postCode: string; /** @pact-example 45 */ age: number; } ```