Skip to content

Commit

Permalink
Merge branch 'master' into update-spy-on-property
Browse files Browse the repository at this point in the history
  • Loading branch information
rickhanlonii authored Mar 8, 2018
2 parents 6d52fcb + 664cdbb commit 4ff102b
Show file tree
Hide file tree
Showing 25 changed files with 332 additions and 69 deletions.
1 change: 1 addition & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package.json
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,13 @@
([#5670](https://github.com/facebook/jest/pull/5670))
* `[expect]` Add inverse matchers (`expect.not.arrayContaining`, etc.,
[#5517](https://github.com/facebook/jest/pull/5517))
* `[jest-mock]` Add tracking of return values in the `mock` property
([#5738](https://github.com/facebook/jest/issues/5738))
* `[expect]`Add nthCalledWith spy matcher
([#5605](https://github.com/facebook/jest/pull/5605))
* `[jest-cli]` Add `isSerial` property that runners can expose to specify that
they can not run in parallel
[#5706](https://github.com/facebook/jest/pull/5706)
* `[jest-mock]` Update `spyOnProperty` to support spying on the prototype
chain ([#5753](https://github.com/facebook/jest/pull/5753))

Expand Down
6 changes: 5 additions & 1 deletion docs/Configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -703,6 +703,10 @@ async runTests(
): Promise<void>
```

If you need to restrict your test-runner to only run in serial rather then being
executed in parallel your class should have the property `isSerial` to be set as
`true`.

### `setupFiles` [array]

Default: `[]`
Expand Down Expand Up @@ -889,7 +893,7 @@ given to [jsdom](https://github.com/tmpvar/jsdom) such as

##### available in Jest **19.0.0+**

(default: `[ '**/__tests__/**/*.js?(x)', '**/?(*.)(spec|test).js?(x)' ]`)
(default: `[ '**/__tests__/**/*.js?(x)', '**/?(*.)+(spec|test).js?(x)' ]`)

The glob patterns Jest uses to detect test files. By default it looks for `.js`
and `.jsx` files inside of `__tests__` folders, as well as any files with a
Expand Down
19 changes: 19 additions & 0 deletions docs/ExpectAPI.md
Original file line number Diff line number Diff line change
Expand Up @@ -622,6 +622,25 @@ test('applying to all flavors does mango last', () => {
});
```

### `.nthCalledWith(nthCall, arg1, arg2, ....)`

If you have a mock function, you can use `.nthCalledWith` to test what arguments
it was nth called with. For example, let's say you have a
`drinkEach(drink, Array<flavor>)` function that applies `f` to a bunch of
flavors, and you want to ensure that when you call it, the first flavor it
operates on is `'lemon'` and the second one is `'octopus'`. You can write:

Note that, nth argument must be positive integer starting from 1.

```js
test('drinkEach drinks each drink', () => {
const drink = jest.fn();
drinkEach(drink, ['lemon', 'octopus']);
expect(drink).nthCalledWith(1, 'lemon');
expect(drink).nthCalledWith(2, 'octopus');
});
```

### `.toBeCloseTo(number, numDigits)`

Using exact equality with floating point numbers is a bad idea. Rounding means
Expand Down
5 changes: 3 additions & 2 deletions docs/ManualMocks.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,9 @@ accordingly.
```

When a manual mock exists for a given module, Jest's module system will use that
module when explicitly calling `jest.mock('moduleName')`. However, manual mocks
will take precedence over node modules even if `jest.mock('moduleName')` is not
module when explicitly calling `jest.mock('moduleName')`. However, when
`automock` is set to `true`, the manual mock implementation will be used instead
of the automatically created mock, even if `jest.mock('moduleName')` is not
called. To opt out of this behavior you will need to explicitly call
`jest.unmock('moduleName')` in tests that should use the actual module
implementation.
Expand Down
15 changes: 14 additions & 1 deletion docs/MockFunctionAPI.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,26 @@ Each call is represented by an array of arguments that were passed during the
call.

For example: A mock function `f` that has been called twice, with the arguments
`f('arg1', 'arg2')`, and then with the arguments `f('arg3', 'arg4')` would have
`f('arg1', 'arg2')`, and then with the arguments `f('arg3', 'arg4')`, would have
a `mock.calls` array that looks like this:

```js
[['arg1', 'arg2'], ['arg3', 'arg4']];
```

### `mockFn.mock.returnValues`

An array containing values that have been returned by all calls to this mock
function.

For example: A mock function `f` that has been called twice, returning
`result1`, and then returning `result2`, would have a `mock.returnValues` array
that looks like this:

```js
['result1', 'result2'];
```

### `mockFn.mock.instances`

An array that contains all the object instances that have been instantiated from
Expand Down
15 changes: 11 additions & 4 deletions docs/MockFunctions.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,17 @@ expect(mockCallback.mock.calls[0][0]).toBe(0);

// The first argument of the second call to the function was 1
expect(mockCallback.mock.calls[1][0]).toBe(1);

// The return value of the first call to the function was 42
expect(mockCallback.mock.returnValues[0]).toBe(42);
```

## `.mock` property

All mock functions have this special `.mock` property, which is where data about
how the function has been called is kept. The `.mock` property also tracks the
value of `this` for each call, so it is possible to inspect this as well:
how the function has been called and what the function returned is kept. The
`.mock` property also tracks the value of `this` for each call, so it is
possible to inspect this as well:

```javascript
const myMock = jest.fn();
Expand All @@ -62,7 +66,7 @@ console.log(myMock.mock.instances);
```

These mock members are very useful in tests to assert how these functions get
called, or instantiated:
called, instantiated, or what they returned:

```javascript
// The function was called exactly once
Expand All @@ -74,6 +78,9 @@ expect(someMockFunction.mock.calls[0][0]).toBe('first arg');
// The second arg of the first call to the function was 'second arg'
expect(someMockFunction.mock.calls[0][1]).toBe('second arg');

// The return value of the first call to the function was 'return value'
expect(someMockFunction.mock.returnValues[0]).toBe('return value');

// This function was instantiated exactly twice
expect(someMockFunction.mock.instances.length).toBe(2);

Expand Down Expand Up @@ -165,7 +172,7 @@ test('should fetch users', () => {
const resp = {data: [{name: 'Bob'}]};
axios.get.mockResolvedValue(resp);

// or you could use the follwing depending on your use case:
// or you could use the following depending on your use case:
// axios.get.mockImpementation(() => Promise.resolve(resp))

return Users.all().then(users => expect(users).toEqual(resp.data));
Expand Down
2 changes: 1 addition & 1 deletion examples/typescript/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,6 @@
"transform": {
"^.+\\.(ts|tsx)$": "<rootDir>/preprocessor.js"
},
"testMatch": ["**/__tests__/*.(ts|tsx|js)"]
"testMatch": ["**/__tests__/*.+(ts|tsx|js)"]
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ exports[`--showConfig outputs config info and exits 1`] = `
\\"testLocationInResults\\": false,
\\"testMatch\\": [
\\"**/__tests__/**/*.js?(x)\\",
\\"**/?(*.)(spec|test).js?(x)\\"
\\"**/?(*.)+(spec|test).js?(x)\\"
],
\\"testPathIgnorePatterns\\": [
\\"/node_modules/\\"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Copyright 2004-present Facebook. All Rights Reserved.

it('adds 1 + 2 to equal 3 in TScript', ()=> {
it('adds 1 + 2 to equal 3 in TScript', () => {
const sum = require('../covered.ts');
expect(sum(1, 2)).toBe(3);
});
2 changes: 1 addition & 1 deletion integration-tests/typescript-coverage/covered.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@

export = function sum(a: number, b: number): number {
return a + b;
}
};
2 changes: 1 addition & 1 deletion integration-tests/typescript-coverage/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"transform": {
"^.+\\.(ts|js)$": "<rootDir>/TypescriptPreprocessor.js"
},
"testMatch": ["**/__tests__/*.(ts|tsx|js)"],
"testMatch": ["**/__tests__/*.+(ts|tsx|js)"],
"testEnvironment": "node",
"moduleFileExtensions": [
"ts",
Expand Down
38 changes: 38 additions & 0 deletions packages/expect/src/__tests__/__snapshots__/extend.test.js.snap
Original file line number Diff line number Diff line change
@@ -1,5 +1,43 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`defines asymmetric matchers 1`] = `
"<dim>expect(</><red>received</><dim>).toEqual(</><green>expected</><dim>)</>
Expected value to equal:
<green>{\\"value\\": toBeDivisibleBy<2>}</>
Received:
<red>{\\"value\\": 3}</>
Difference:
<green>- Expected</>
<red>+ Received</>
<dim> Object {</>
<green>- \\"value\\": toBeDivisibleBy<2>,</>
<red>+ \\"value\\": 3,</>
<dim> }</>"
`;
exports[`defines asymmetric matchers that can be prefixed by not 1`] = `
"<dim>expect(</><red>received</><dim>).toEqual(</><green>expected</><dim>)</>
Expected value to equal:
<green>{\\"value\\": not.toBeDivisibleBy<2>}</>
Received:
<red>{\\"value\\": 2}</>
Difference:
<green>- Expected</>
<red>+ Received</>
<dim> Object {</>
<green>- \\"value\\": not.toBeDivisibleBy<2>,</>
<red>+ \\"value\\": 2,</>
<dim> }</>"
`;
exports[`is available globally 1`] = `"expected 15 to be divisible by 2"`;
exports[`is ok if there is no message specified 1`] = `"<red>No message was specified for this matcher.</>"`;
18 changes: 18 additions & 0 deletions packages/expect/src/__tests__/extend.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,3 +70,21 @@ it('exposes an equality function to custom matchers', () => {

expect(() => jestExpect().toBeOne()).not.toThrow();
});

it('defines asymmetric matchers', () => {
expect(() =>
jestExpect({value: 2}).toEqual({value: jestExpect.toBeDivisibleBy(2)}),
).not.toThrow();
expect(() =>
jestExpect({value: 3}).toEqual({value: jestExpect.toBeDivisibleBy(2)}),
).toThrowErrorMatchingSnapshot();
});

it('defines asymmetric matchers that can be prefixed by not', () => {
expect(() =>
jestExpect({value: 2}).toEqual({value: jestExpect.not.toBeDivisibleBy(2)}),
).toThrowErrorMatchingSnapshot();
expect(() =>
jestExpect({value: 3}).toEqual({value: jestExpect.not.toBeDivisibleBy(2)}),
).not.toThrow();
});
2 changes: 1 addition & 1 deletion packages/expect/src/asymmetric_matchers.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import {

import {emptyObject} from './utils';

class AsymmetricMatcher {
export class AsymmetricMatcher {
$$typeof: Symbol;
inverse: boolean;

Expand Down
10 changes: 5 additions & 5 deletions packages/expect/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,7 @@ const makeThrowingMatcher = (
};

expect.extend = (matchers: MatchersObject): void =>
setMatchers(matchers, false);
setMatchers(matchers, false, expect);

expect.anything = anything;
expect.any = any;
Expand Down Expand Up @@ -294,15 +294,15 @@ const _validateResult = result => {
};

// add default jest matchers
setMatchers(matchers, true);
setMatchers(spyMatchers, true);
setMatchers(toThrowMatchers, true);
setMatchers(matchers, true, expect);
setMatchers(spyMatchers, true, expect);
setMatchers(toThrowMatchers, true, expect);

expect.addSnapshotSerializer = () => void 0;
expect.assertions = (expected: number) => {
getState().expectedAssertionsNumber = expected;
};
expect.hasAssertions = expected => {
expect.hasAssertions = (expected: any) => {
utils.ensureNoExpected(expected, '.hasAssertions');
getState().isExpectingAssertions = true;
};
Expand Down
50 changes: 48 additions & 2 deletions packages/expect/src/jest_matchers_object.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
* @flow
*/

import type {MatchersObject} from 'types/Matchers';
import {AsymmetricMatcher} from './asymmetric_matchers';
import type {Expect, MatchersObject} from 'types/Matchers';

// Global matchers object holds the list of available matchers and
// the state, that can hold matcher specific values that change over time.
Expand Down Expand Up @@ -39,12 +40,57 @@ export const setState = (state: Object) => {

export const getMatchers = () => global[JEST_MATCHERS_OBJECT].matchers;

export const setMatchers = (matchers: MatchersObject, isInternal: boolean) => {
export const setMatchers = (
matchers: MatchersObject,
isInternal: boolean,
expect: Expect,
) => {
Object.keys(matchers).forEach(key => {
const matcher = matchers[key];
Object.defineProperty(matcher, INTERNAL_MATCHER_FLAG, {
value: isInternal,
});

if (!isInternal) {
// expect is defined

class CustomMatcher extends AsymmetricMatcher {
sample: any;

constructor(sample: any, inverse: boolean = false) {
super();
this.sample = sample;
this.inverse = inverse;
}

asymmetricMatch(other: any) {
const {pass}: {message: () => string, pass: boolean} = matcher(
(other: any),
(this.sample: any),
);

return this.inverse ? !pass : pass;
}

toString() {
return `${this.inverse ? 'not.' : ''}${key}`;
}

getExpectedType() {
return 'any';
}

toAsymmetricMatcher() {
return `${this.toString()}<${this.sample}>`;
}
}

expect[key] = (sample: any) => new CustomMatcher(sample);
if (!expect.not) {
expect.not = {};
}
expect.not[key] = (sample: any) => new CustomMatcher(sample, true);
}
});

Object.assign(global[JEST_MATCHERS_OBJECT].matchers, matchers);
Expand Down
Loading

0 comments on commit 4ff102b

Please sign in to comment.