Skip to content

Commit

Permalink
Add support for async matchers
Browse files Browse the repository at this point in the history
  • Loading branch information
bilby91 committed Apr 10, 2018
1 parent 89d98d3 commit 6a6e6a1
Show file tree
Hide file tree
Showing 7 changed files with 233 additions and 61 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

### Features

* `[expect]` Add support for async matchers
([#5836](https://github.com/facebook/jest/pull/5919))
* `[jest-config]` Export Jest's default options
([#5948](https://github.com/facebook/jest/pull/5948))
* `[jest-editor-support]` Move `coverage` to `ProjectWorkspace.collectCoverage`
Expand Down
68 changes: 36 additions & 32 deletions flow-typed/npm/jest_v21.x.x.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ type JestMockFn<TArguments: $ReadOnlyArray<*>, TReturn> = {
* An array that contains all the object instances that have been
* instantiated from this mock function.
*/
instances: Array<TReturn>
instances: Array<TReturn>,
},
/**
* Resets all information stored in the mockFn.mock.calls and
Expand Down Expand Up @@ -45,15 +45,15 @@ type JestMockFn<TArguments: $ReadOnlyArray<*>, TReturn> = {
* will also be executed when the mock is called.
*/
mockImplementation(
fn: (...args: TArguments) => TReturn
fn: (...args: TArguments) => TReturn,
): JestMockFn<TArguments, TReturn>,
/**
* Accepts a function that will be used as an implementation of the mock for
* one call to the mocked function. Can be chained so that multiple function
* calls produce different results.
*/
mockImplementationOnce(
fn: (...args: TArguments) => TReturn
fn: (...args: TArguments) => TReturn,
): JestMockFn<TArguments, TReturn>,
/**
* Just a simple sugar function for returning `this`
Expand All @@ -66,14 +66,14 @@ type JestMockFn<TArguments: $ReadOnlyArray<*>, TReturn> = {
/**
* Sugar for only returning a value once inside your mock
*/
mockReturnValueOnce(value: TReturn): JestMockFn<TArguments, TReturn>
mockReturnValueOnce(value: TReturn): JestMockFn<TArguments, TReturn>,
};

type JestAsymmetricEqualityType = {
/**
* A custom Jasmine equality tester
*/
asymmetricMatch(value: mixed): boolean
asymmetricMatch(value: mixed): boolean,
};

type JestCallsType = {
Expand All @@ -83,21 +83,25 @@ type JestCallsType = {
count(): number,
first(): mixed,
mostRecent(): mixed,
reset(): void
reset(): void,
};

type JestClockType = {
install(): void,
mockDate(date: Date): void,
tick(milliseconds?: number): void,
uninstall(): void
uninstall(): void,
};

type JestMatcherResult = {
type JestMatcherSyncResult = {
message?: string | (() => string),
pass: boolean
pass: boolean,
};

type JestMatcherAsyncResult = Promise<JestMatcherSyncResult>;

type JestMatcherResult = JestMatcherSyncResult | JestMatcherAsyncResult;

type JestMatcher = (actual: any, expected: any) => JestMatcherResult;

type JestPromiseType = {
Expand All @@ -110,7 +114,7 @@ type JestPromiseType = {
* Use resolves to unwrap the value of a fulfilled promise so any other
* matcher can be chained. If the promise is rejected the assertion fails.
*/
resolves: JestExpectType
resolves: JestExpectType,
};

/**
Expand All @@ -133,7 +137,7 @@ type EnzymeMatchersType = {
toIncludeText(text: string): void,
toHaveValue(value: any): void,
toMatchElement(element: React$Element<any>): void,
toMatchSelector(selector: string): void
toMatchSelector(selector: string): void,
};

type JestExpectType = {
Expand Down Expand Up @@ -277,7 +281,7 @@ type JestExpectType = {
* Use .toThrowErrorMatchingSnapshot to test that a function throws a error
* matching the most recent snapshot when it is called.
*/
toThrowErrorMatchingSnapshot(): void
toThrowErrorMatchingSnapshot(): void,
};

type JestObjectType = {
Expand Down Expand Up @@ -329,7 +333,7 @@ type JestObjectType = {
* implementation.
*/
fn<TArguments: $ReadOnlyArray<*>, TReturn>(
implementation?: (...args: TArguments) => TReturn
implementation?: (...args: TArguments) => TReturn,
): JestMockFn<TArguments, TReturn>,
/**
* Determines if the given function is a mocked function.
Expand All @@ -352,7 +356,7 @@ type JestObjectType = {
mock(
moduleName: string,
moduleFactory?: any,
options?: Object
options?: Object,
): JestObjectType,
/**
* Returns the actual module instead of a mock, bypassing all checks on
Expand Down Expand Up @@ -420,32 +424,32 @@ type JestObjectType = {
* Creates a mock function similar to jest.fn but also tracks calls to
* object[methodName].
*/
spyOn(object: Object, methodName: string): JestMockFn<any, any>
spyOn(object: Object, methodName: string): JestMockFn<any, any>,
};

type JestSpyType = {
calls: JestCallsType
calls: JestCallsType,
};

/** Runs this function after every test inside this context */
declare function afterEach(
fn: (done: () => void) => ?Promise<mixed>,
timeout?: number
timeout?: number,
): void;
/** Runs this function before every test inside this context */
declare function beforeEach(
fn: (done: () => void) => ?Promise<mixed>,
timeout?: number
timeout?: number,
): void;
/** Runs this function after all tests have finished inside this context */
declare function afterAll(
fn: (done: () => void) => ?Promise<mixed>,
timeout?: number
timeout?: number,
): void;
/** Runs this function before any tests have started inside this context */
declare function beforeAll(
fn: (done: () => void) => ?Promise<mixed>,
timeout?: number
timeout?: number,
): void;

/** A context for grouping tests together */
Expand All @@ -463,7 +467,7 @@ declare var describe: {
/**
* Skip running this describe block
*/
skip(name: string, fn: () => void): void
skip(name: string, fn: () => void): void,
};

/** An individual test unit */
Expand All @@ -478,7 +482,7 @@ declare var it: {
(
name: string,
fn?: (done: () => void) => ?Promise<mixed>,
timeout?: number
timeout?: number,
): void,
/**
* Only run this test
Expand All @@ -490,7 +494,7 @@ declare var it: {
only(
name: string,
fn?: (done: () => void) => ?Promise<mixed>,
timeout?: number
timeout?: number,
): void,
/**
* Skip running this test
Expand All @@ -502,7 +506,7 @@ declare var it: {
skip(
name: string,
fn?: (done: () => void) => ?Promise<mixed>,
timeout?: number
timeout?: number,
): void,
/**
* Run the test concurrently
Expand All @@ -514,13 +518,13 @@ declare var it: {
concurrent(
name: string,
fn?: (done: () => void) => ?Promise<mixed>,
timeout?: number
): void
timeout?: number,
): void,
};
declare function fit(
name: string,
fn: (done: () => void) => ?Promise<mixed>,
timeout?: number
timeout?: number,
): void;
/** An individual test unit */
declare var test: typeof it;
Expand All @@ -538,7 +542,7 @@ declare var expect: {
/** The object that you want to make assertions against */
(value: any): JestExpectType & JestPromiseType & EnzymeMatchersType,
/** Add additional Jasmine matchers to Jest's roster */
extend(matchers: { [name: string]: JestMatcher }): void,
extend(matchers: {[name: string]: JestMatcher}): void,
/** Add a module that formats application-specific data structures. */
addSnapshotSerializer(serializer: (input: Object) => string): void,
assertions(expectedAssertions: number): void,
Expand All @@ -549,7 +553,7 @@ declare var expect: {
objectContaining(value: Object): void,
/** Matches any received string that contains the exact expected string. */
stringContaining(value: string): void,
stringMatching(value: string | RegExp): void
stringMatching(value: string | RegExp): void,
};

// TODO handle return type
Expand All @@ -572,8 +576,8 @@ declare var jasmine: {
createSpy(name: string): JestSpyType,
createSpyObj(
baseName: string,
methodNames: Array<string>
): { [methodName: string]: JestSpyType },
methodNames: Array<string>,
): {[methodName: string]: JestSpyType},
objectContaining(value: Object): void,
stringMatching(value: string): void
stringMatching(value: string): void,
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`works with expected non promise values and not is a failure 1`] = `
"Expected value to not have length:
2
Received:
1,2
received.length:
2"
`;

exports[`works with expected non promise values is a failure 1`] = `
"Expected value to have length:
2
Received:
1
received.length:
1"
`;

exports[`works with expected promise values and not is a failure 1`] = `
"Expected value to not have length:
2
Received:
1,2
received.length:
2"
`;

exports[`works with expected promise values is a failure 1`] = `
"Expected value to have length:
2
Received:
1
received.length:
1"
`;
100 changes: 100 additions & 0 deletions integration-tests/__tests__/expect_async_matcher.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/**
* Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/

'use strict';

async function toHaveLengthAsync(
received: any,
lengthPromise: Promise<number>,
) {
const length = await lengthPromise;

const pass = received.length === length;
const message = pass
? () =>
`Expected value to not have length:\n` +
` ${length}\n` +
`Received:\n` +
` ${received}\n` +
`received.length:\n` +
` ${received.length}`
: () =>
`Expected value to have length:\n` +
` ${length}\n` +
`Received:\n` +
` ${received}\n` +
`received.length:\n` +
` ${received.length}`;

return {message, pass};
}

expect.extend({
toHaveLengthAsync,
});

it('works with expected non promise values', async () => {
await (expect([1]): any).toHaveLengthAsync(Promise.resolve(1));
});

it('works with expected non promise values is a failure', async () => {
try {
await (expect([1]): any).toHaveLengthAsync(Promise.resolve(2));
} catch (error) {
expect(error).toBeDefined();
expect(error.message).toMatchSnapshot();
}
});

it('works with expected non promise values and not', async () => {
await (expect([1, 2]): any).not.toHaveLengthAsync(Promise.resolve(1));
});

it('works with expected non promise values and not is a failure', async () => {
try {
await (expect([1, 2]): any).not.toHaveLengthAsync(Promise.resolve(2));
} catch (error) {
expect(error).toBeDefined();
expect(error.message).toMatchSnapshot();
}
});

it('works with expected promise values', async () => {
await (expect(Promise.resolve([1])).resolves: any).toHaveLengthAsync(
Promise.resolve(1),
);
});

it('works with expected promise values is a failure', async () => {
try {
await (expect(Promise.resolve([1])): any).resolves.toHaveLengthAsync(
Promise.resolve(2),
);
} catch (error) {
expect(error).toBeDefined();
expect(error.message).toMatchSnapshot();
}
});

it('works with expected promise values and not', async () => {
await (expect(Promise.resolve([1, 2])).resolves.not: any).toHaveLengthAsync(
Promise.resolve(1),
);
});

it('works with expected promise values and not is a failure', async () => {
try {
await (expect(Promise.resolve([1, 2])).resolves.not: any).toHaveLengthAsync(
Promise.resolve(2),
);
} catch (error) {
expect(error).toBeDefined();
expect(error.message).toMatchSnapshot();
}
});
Loading

0 comments on commit 6a6e6a1

Please sign in to comment.