Skip to content

Commit

Permalink
feat(messagePact): Add messagePactWith so that the default options ar…
Browse files Browse the repository at this point in the history
…e available for message pact users too
  • Loading branch information
TimothyJones committed Feb 23, 2021
1 parent 540dc3c commit a8c1943
Show file tree
Hide file tree
Showing 10 changed files with 326 additions and 116 deletions.
26 changes: 16 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -189,13 +189,12 @@ pactWith({ consumer: 'MyConsumer', provider: 'MyProvider' }, provider => {
# API Documentation
Jest-Pact has three functions:
Jest-Pact has two primary functions:
- `pactWith(JestPactOptions, (providerMock) => { /* tests go here */ })`: a wrapper that sets up a pact mock provider
- `xpactWith(JestPactOptions, (providerMock) => { /* tests go here */ })`: Like `xdescribe` in Jest, this skips the pact tests described within.
- `fpactWith(JestPactOptions, (providerMock) => { /* tests go here */ })`: Like `fdescribe` in Jest, this sets this test suite to only run this test.
- `pactWith(JestPactOptions, (providerMock) => { /* tests go here */ })`: a wrapper that sets up a pact mock provider, applies sensible default options, and applies the setup and verification hooks so you don't have to
- `messagePactWith(JestMessageConsumerOptions, (messagePact) => { /* tests go here */ })`: a wrapper that sets up a message pact instance and applies sensible default options
Additionally, `pactWith.only` and `pactWith.skip` behave as you would expect from Jest.
Additionally, `pactWith.only / fpactWith`, `pactWith.skip / xpactWith`, `messagePactWith.only / fmessagePactWith` and `messagePactWith.skip / xmessagePactWith` behave as you would expect from Jest.
There are two types exported:
Expand All @@ -208,16 +207,23 @@ You can use all the usual `PactOptions` from pact-js, plus a timeout for
telling jest to wait a bit longer for pact to start and run.
```ts
pactWith(JestPactOptions, provider => {
// regular pact tests go here
}
pactWith(JestPactOptions, (provider) => {
// regular http pact tests go here
});
messagePactWith(JestMessageConsumerOptions, (messagePact) => {
// regular message pact tests go here
});

interface JestPactOptions = PactOptions & {
interface ExtraOptions {
timeout?: number; // Timeout for pact service start/teardown, expressed in milliseconds
// Default is 30000 milliseconds (30 seconds).
// Default is 30000 milliseconds (30 seconds).
logDir?: string; // path for the log file
logFileName?: string; // filename for the log file
}

type JestPactOptions = PactOptions & ExtraOptions;

type JestMessageConsumerOptions = MessageConsumerOptions & ExtraOptions;
```
### Defaults
Expand Down
109 changes: 3 additions & 106 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,106 +1,3 @@
import * as pact from '@pact-foundation/pact';
import { LogLevel, PactOptions } from '@pact-foundation/pact/dsl/options';
import * as path from 'path';

export type JestPactOptions = PactOptions & {
timeout?: number;
logDir?: string;
logFileName?: string;
};

export type JestProvidedPactFn = (provider: pact.Pact) => void;

const logHint = (options: JestPactOptions) =>
options.port ? `-port-${options.port}` : '';

const applyDefaults = (options: JestPactOptions) => ({
log: path.resolve(
options.logDir ? options.logDir : path.join(process.cwd(), 'pact', 'logs'),
options.logFileName
? options.logFileName
: `${options.consumer}-${
options.provider
}-mockserver-interaction${logHint(options)}.log`,
),
dir: path.resolve(process.cwd(), 'pact/pacts'),
spec: 2,
logLevel: 'warn' as LogLevel,
pactfileWriteMode: 'update' as pact.PactfileWriteMode,
...options,
});

const setupProvider = (options: JestPactOptions) => {
const pactMock: pact.Pact = new pact.Pact(options);

beforeAll(() => pactMock.setup());
afterAll(() => pactMock.finalize());
afterEach(() => pactMock.verify());

return pactMock;
};

// This should be moved to pact-js, probably
export const getProviderBaseUrl = (provider: pact.Pact) =>
provider.mockService
? provider.mockService.baseUrl
: `http://${provider.opts.host}:${provider.opts.port}`;

const jestPactWrapper = (
options: JestPactOptions,
tests: JestProvidedPactFn,
): void => {
const pactTestTimeout = options.timeout || 30000;

describe(`with ${pactTestTimeout} ms timeout for Pact`, () => {
let originalTimeout: number;

beforeAll(() => {
// Jest's default timeout is 5000, and jest doesn't provide a way of
// asking what the current timeout is. In Jest 24 and 25, Jasmine was probably
// the test runner, so we can ask Jasmine if it is there. In later versions of
// Jest (eg 26 and up), Jasmine may not be defined.
// See https://github.com/pact-foundation/jest-pact/issues/197 for discussion
//
// For now, we just assume that 5000 was the original timeout.
// The impact is likely to be small, as `jest.setTimeout()` only works for the
// current test file
originalTimeout = global.jasmine
? global.jasmine.DEFAULT_TIMEOUT_INTERVAL
: 5000;
jest.setTimeout(pactTestTimeout);
});

afterAll(() => {
jest.setTimeout(originalTimeout);
});

tests(setupProvider(applyDefaults(options)));
});
};

const describeString = (options: JestPactOptions) =>
`Pact between ${options.consumer} and ${options.provider}`;

type PactWithFn = (options: JestPactOptions, tests: JestProvidedPactFn) => void;
interface PactWith {
(options: JestPactOptions, tests: JestProvidedPactFn): void;
only: PactWithFn;
skip: PactWithFn;
}

const describePactWith = (describeFn: jest.Describe): PactWithFn => (
options: JestPactOptions,
tests: JestProvidedPactFn,
) => describeFn(describeString(options), () => jestPactWrapper(options, tests));

const extend = (pactWithfn: PactWithFn) => {
const ret = pactWithfn as PactWith;
ret.only = describePactWith(describe.only);
ret.skip = describePactWith(describe.skip);
return ret;
};

export const pactWith = extend(describePactWith(describe));

export const xpactWith = pactWith.skip;
export const fpactWith = pactWith.only;
export * from './messagePactWith';
export * from './types';
export * from './pactWith';
39 changes: 39 additions & 0 deletions src/internal/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { PactfileWriteMode } from '@pact-foundation/pact';
import { LogLevel } from '@pact-foundation/pact/dsl/options';
import * as path from 'path';
import { JestMessageConsumerOptions, JestPactOptions } from '../types';

const logHint = (options: JestPactOptions) =>
options.port ? `-port-${options.port}` : '';

const applyCommonDefaults = (
options: JestPactOptions | JestMessageConsumerOptions,
) => ({
log: path.resolve(
options.logDir ? options.logDir : path.join(process.cwd(), 'pact', 'logs'),
options.logFileName
? options.logFileName
: `${options.consumer}-${
options.provider
}-mockserver-interaction${logHint(options)}.log`,
),
dir: path.resolve(process.cwd(), 'pact/pacts'),
logLevel: 'warn' as LogLevel,
pactfileWriteMode: 'update' as PactfileWriteMode,
});

export const applyPactOptionDefaults = (
options: JestPactOptions,
): JestPactOptions => ({
...applyCommonDefaults(options),
spec: 2,
...options,
});

export const applyMessagePactOptionDefaults = (
options: JestMessageConsumerOptions,
): JestMessageConsumerOptions => ({
...applyCommonDefaults(options),
spec: 3,
...options,
});
34 changes: 34 additions & 0 deletions src/internal/scaffold.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import {
MessageConsumerOptions,
PactOptions,
} from '@pact-foundation/pact/dsl/options';
import { ConsumerOptions, WrapperFn, WrapperWithOnlyAndSkip } from './types';

const describeString = (options: PactOptions | MessageConsumerOptions) =>
`Pact between ${options.consumer} and ${options.provider}`;

const describePactWith = <
O extends ConsumerOptions,
P,
W extends WrapperFn<O, P>
>(
describeFn: jest.Describe,
wrapper: W,
) => (options: O, tests: P) =>
describeFn(describeString(options), () => wrapper(options, tests));

export const extendPactWith = <
O extends ConsumerOptions,
P,
W extends WrapperFn<O, P>
>(
wrapper: W,
) => {
const ret = describePactWith<O, P, W>(
describe,
wrapper,
) as WrapperWithOnlyAndSkip<O, P>;
ret.only = describePactWith<O, P, W>(describe.only, wrapper);
ret.skip = describePactWith<O, P, W>(describe.skip, wrapper);
return ret;
};
12 changes: 12 additions & 0 deletions src/internal/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
export interface ConsumerOptions {
consumer: string;
provider: string;
}

export type WrapperFn<O extends ConsumerOptions, P> = (o: O, p: P) => void;

export interface WrapperWithOnlyAndSkip<O extends ConsumerOptions, P>
extends WrapperFn<O, P> {
only: WrapperFn<O, P>;
skip: WrapperFn<O, P>;
}
36 changes: 36 additions & 0 deletions src/internal/withTimeout.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
interface TimeoutOption {
timeout?: number;
}

export const withTimeout = (
options: TimeoutOption,
tests: () => void,
): void => {
const pactTestTimeout = options.timeout || 30000;

describe(`with ${pactTestTimeout} ms timeout for Pact`, () => {
let originalTimeout: number;

beforeAll(() => {
// Jest's default timeout is 5000, and jest doesn't provide a way of
// asking what the current timeout is. In Jest 24 and 25, Jasmine was probably
// the test runner, so we can ask Jasmine if it is there. In later versions of
// Jest (eg 26 and up), Jasmine may not be defined.
// See https://github.com/pact-foundation/jest-pact/issues/197 for discussion
//
// For now, we just assume that 5000 was the original timeout.
// The impact is likely to be small, as `jest.setTimeout()` only works for the
// current test file
originalTimeout = global.jasmine
? global.jasmine.DEFAULT_TIMEOUT_INTERVAL
: 5000;
jest.setTimeout(pactTestTimeout);
});

afterAll(() => {
jest.setTimeout(originalTimeout);
});

tests();
});
};
30 changes: 30 additions & 0 deletions src/messagePactWith.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { MessageConsumerPact } from '@pact-foundation/pact';

import { applyMessagePactOptionDefaults } from './internal/config';
import { WrapperFn } from './internal/types';
import { withTimeout } from './internal/withTimeout';

import { extendPactWith } from './internal/scaffold';
import { JestMessageConsumerOptions, JestProvidedMessagePactFn } from './types';

const setupMessageProvider = (
options: JestMessageConsumerOptions,
): MessageConsumerPact => new MessageConsumerPact(options);

const jestMessagePactWrapper = (
options: JestMessageConsumerOptions,
tests: JestProvidedMessagePactFn,
): void => {
withTimeout(options, () => {
tests(setupMessageProvider(applyMessagePactOptionDefaults(options)));
});
};

export const messagePactWith = extendPactWith<
JestMessageConsumerOptions,
JestProvidedMessagePactFn,
WrapperFn<JestMessageConsumerOptions, JestProvidedMessagePactFn>
>(jestMessagePactWrapper);

export const xmessagePactWith = messagePactWith.skip;
export const fmessagePactWith = messagePactWith.only;
41 changes: 41 additions & 0 deletions src/pactWith.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { Pact } from '@pact-foundation/pact';
import { applyPactOptionDefaults } from './internal/config';
import { WrapperFn } from './internal/types';
import { withTimeout } from './internal/withTimeout';

import { extendPactWith } from './internal/scaffold';
import { JestPactOptions, JestProvidedPactFn } from './types';

const setupProvider = (options: JestPactOptions): Pact => {
const pactMock: Pact = new Pact(options);

beforeAll(() => pactMock.setup());
afterAll(() => pactMock.finalize());
afterEach(() => pactMock.verify());

return pactMock;
};

// This should be moved to pact-js, probably
export const getProviderBaseUrl = (provider: Pact) =>
provider.mockService
? provider.mockService.baseUrl
: `http://${provider.opts.host}:${provider.opts.port}`;

const pactWithWrapper = (
options: JestPactOptions,
tests: JestProvidedPactFn,
): void => {
withTimeout(options, () => {
tests(setupProvider(applyPactOptionDefaults(options)));
});
};

export const pactWith = extendPactWith<
JestPactOptions,
JestProvidedPactFn,
WrapperFn<JestPactOptions, JestProvidedPactFn>
>(pactWithWrapper);

export const xpactWith = pactWith.skip;
export const fpactWith = pactWith.only;
Loading

0 comments on commit a8c1943

Please sign in to comment.