Skip to content

Commit

Permalink
Expose jasmine asymmetric matches as part of Jest public API (jestjs#…
Browse files Browse the repository at this point in the history
  • Loading branch information
just-boris authored and cpojer committed Dec 8, 2016
1 parent 4a8cfd6 commit ca4e980
Show file tree
Hide file tree
Showing 4 changed files with 212 additions and 0 deletions.
49 changes: 49 additions & 0 deletions docs/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ In your test files, Jest puts each of these methods and objects into the global
- [`describe(name, fn)`](#basic-testing)
- [`expect(value)`](#expectvalue)
- [`expect.extend(matchers)`](#extending-jest-matchers)
- [`expect.<asymmetric-match>()`](#asymmetric-jest-matchers)
- [`it(name, fn)`](#basic-testing)
- [`it.only(name, fn)`](#basic-testing)
- [`it.skip(name, fn)`](#basic-testing)
Expand Down Expand Up @@ -812,6 +813,54 @@ This will print something like this:

When an assertion fails, the error message should give as much signal as necessary to the user so they can resolve their issue quickly. It's usually recommended to spend a lot of time crafting a great failure message to make sure users of your custom assertions have a good developer experience.

### Asymmetric Jest Matchers

Sometimes you don't want to check equality of entire object. You just need to assert that value is not empty or has some expected type. For example, we want to check the shape of some message entity:

```
expect({
timestamp: 1480807810388,
text: 'Some text content, but we care only about *this part*'
}).toEqual({
timestamp: expect.any(Number),
text: expect.stringMatching('*this part*')
});
```

There some special values with specific comparing behavior that you can use as a part of expectation. They are useful for asserting some types of data, like timestamps, or long text resources, where only part of it is important for testing. Currently, Jest has the following asymmetric matches:

* `expect.anything()` - matches everything, except `null` and `undefined`
* `expect.any(<constructor>)` - checks, that actual value is instance of provided `<constructor>`.
* `expect.objectContaining(<object>)` - compares only keys, that exist in provided object. All other keys of `actual` value will be ignored.
* `expect.arrayContaining(<array>)` - checks that all items from the provided `array` are exist in `actual` value. It allows to have more values in `actual`.
* `expect.stringMatching(<string|Regexp>)` - checks that actual value has matches of provided expectation.

These expressions can be used as an argument in `.toEqual` and `.toBeCalledWith`:

```
expect(callback).toEqual(expect.any(Function));
expect(mySpy).toBeCalledWith(expect.any(Number), expect.any(String))
```

They can be also used as object keys and may be nested into each other:

```
expect(myObject).toEqual(expect.objectContaining({
items: expect.arrayContaining([
expect.any(Number)
])
}));
```

The example above will match the following object. Array may contain more items, as well as object itself may also have some extra keys:

```
{
items: [1]
}
```

## Mock Functions

### `mockFn.mock.calls`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1746,6 +1746,30 @@ Received:
\"abc\""
`;

exports[`.toEqual() expect("abcd").not.toEqual({"regexp": /bc/}) 1`] = `
"expect(received).not.toEqual(expected)

Expected value to not equal:
{\"regexp\": /bc/}
Received:
\"abcd\""
`;

exports[`.toEqual() expect("abd").toEqual({"regexp": /bc/i}) 1`] = `
"expect(received).toEqual(expected)

Expected value to equal:
{\"regexp\": /bc/i}
Received:
\"abd\"

Difference:

Comparing two different types of values:
Expected: object
Received: string"
`;

exports[`.toEqual() expect("banana").toEqual("apple") 1`] = `
"expect(received).toEqual(expected)

Expand All @@ -1755,6 +1779,81 @@ Received:
\"banana\""
`;

exports[`.toEqual() expect([1, 2, 3]).not.toEqual({"sample": [2, 3]}) 1`] = `
"expect(received).not.toEqual(expected)

Expected value to not equal:
{\"sample\": [2, 3]}
Received:
[1, 2, 3]"
`;

exports[`.toEqual() expect([1, 3]).toEqual({"sample": [1, 2]}) 1`] = `
"expect(received).toEqual(expected)

Expected value to equal:
{\"sample\": [1, 2]}
Received:
[1, 3]

Difference:

Comparing two different types of values:
Expected: object
Received: array"
`;

exports[`.toEqual() expect([Function anonymous]).not.toEqual({"expectedObject": [Function Function]}) 1`] = `
"expect(received).not.toEqual(expected)

Expected value to not equal:
{\"expectedObject\": [Function Function]}
Received:
[Function anonymous]"
`;

exports[`.toEqual() expect({"a": 1, "b": [Function anonymous], "c": true}).not.toEqual({"a": 1, "b": {"expectedObject": [Function Function]}, "c": {}}) 1`] = `
"expect(received).not.toEqual(expected)

Expected value to not equal:
{\"a\": 1, \"b\": {\"expectedObject\": [Function Function]}, \"c\": {}}
Received:
{\"a\": 1, \"b\": [Function anonymous], \"c\": true}"
`;

exports[`.toEqual() expect({"a": 1, "b": 2}).not.toEqual({"sample": {"a": 1}}) 1`] = `
"expect(received).not.toEqual(expected)

Expected value to not equal:
{\"sample\": {\"a\": 1}}
Received:
{\"a\": 1, \"b\": 2}"
`;

exports[`.toEqual() expect({"a": 1, "b": 2}).toEqual({"sample": {"a": 2}}) 1`] = `
"expect(received).toEqual(expected)

Expected value to equal:
{\"sample\": {\"a\": 2}}
Received:
{\"a\": 1, \"b\": 2}

Difference:

- Expected
+ Received

@@ -1,5 +1,4 @@
-ObjectContaining {
- \"sample\": Object {
- \"a\": 2,
- },
+Object {
+ \"a\": 1,
+ \"b\": 2,
 }"
`;

exports[`.toEqual() expect({"a": 5}).toEqual({"b": 6}) 1`] = `
"expect(received).toEqual(expected)

Expand Down Expand Up @@ -1817,6 +1916,15 @@ Comparing two different types of values:
Received: null"
`;

exports[`.toEqual() expect(true).not.toEqual({}) 1`] = `
"expect(received).not.toEqual(expected)

Expected value to not equal:
{}
Received:
true"
`;

exports[`.toEqual() expect(true).not.toEqual(true) 1`] = `
"expect(received).not.toEqual(expected)

Expand All @@ -1835,6 +1943,36 @@ Received:
true"
`;

exports[`.toEqual() expect(undefined).toEqual({"expectedObject": [Function Function]}) 1`] = `
"expect(received).toEqual(expected)

Expected value to equal:
{\"expectedObject\": [Function Function]}
Received:
undefined

Difference:

Comparing two different types of values:
Expected: object
Received: undefined"
`;

exports[`.toEqual() expect(undefined).toEqual({}) 1`] = `
"expect(received).toEqual(expected)

Expected value to equal:
{}
Received:
undefined

Difference:

Comparing two different types of values:
Expected: object
Received: undefined"
`;

exports[`.toHaveLength error cases 1`] = `
"expect(received)[.not].toHaveLength(length)

Expand Down
19 changes: 19 additions & 0 deletions packages/jest-matchers/src/__tests__/matchers-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,11 @@ describe('.toEqual()', () => {
[{a: 5}, {b: 6}],
['banana', 'apple'],
[null, undefined],
[{a: 1, b: 2}, jestExpect.objectContaining({a: 2})],
[[1, 3], jestExpect.arrayContaining([1, 2])],
['abd', jestExpect.stringMatching(/bc/i)],
[undefined, jestExpect.anything()],
[undefined, jestExpect.any(Function)],
].forEach(([a, b]) => {
test(`expect(${stringify(a)}).toEqual(${stringify(b)})`, () => {
expect(() => jestExpect(a).toEqual(b))
Expand All @@ -76,6 +81,20 @@ describe('.toEqual()', () => {
[1, 1],
['abc', 'abc'],
[{a: 99}, {a: 99}],
[{a: 1, b: 2}, jestExpect.objectContaining({a: 1})],
[[1, 2, 3], jestExpect.arrayContaining([2, 3])],
['abcd', jestExpect.stringMatching('bc')],
[true, jestExpect.anything()],
[() => {}, jestExpect.any(Function)],
[{
a: 1,
b: () => {},
c: true,
}, {
a: 1,
b: jestExpect.any(Function),
c: jestExpect.anything(),
}],
].forEach(([a, b]) => {
test(`expect(${stringify(a)}).not.toEqual(${stringify(b)})`, () => {
expect(() => jestExpect(a).not.toEqual(b))
Expand Down
6 changes: 6 additions & 0 deletions packages/jest-matchers/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,12 @@ expect.extend = (matchersObj: MatchersObject): void => {
Object.assign(global[GLOBAL_STATE].matchers, matchersObj);
};

expect.anything = global.jasmine.anything;
expect.any = global.jasmine.any;
expect.objectContaining = global.jasmine.objectContaining;
expect.arrayContaining = global.jasmine.arrayContaining;
expect.stringMatching = global.jasmine.stringMatching;

const _validateResult = result => {
if (
typeof result !== 'object' ||
Expand Down

0 comments on commit ca4e980

Please sign in to comment.