Skip to content

Commit

Permalink
.toThrowError()
Browse files Browse the repository at this point in the history
  • Loading branch information
aaronabramov committed Aug 16, 2016
1 parent 1a58302 commit 9641abf
Show file tree
Hide file tree
Showing 10 changed files with 339 additions and 31 deletions.
3 changes: 3 additions & 0 deletions packages/jest-matcher-utils/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,8 @@
"main": "build/index.js",
"scripts": {
"test": "../../packages/jest-cli/bin/jest.js"
},
"dependencies": {
"chalk": "^1.1.3"
}
}
2 changes: 2 additions & 0 deletions packages/jest-matcher-utils/src/__tests__/index-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,4 +53,6 @@ describe('.getType()', () => {
test('function', () => expect(getType(() => {})).toBe('function'));
test('boolean', () => expect(getType(true)).toBe('boolean'));
test('symbol', () => expect(getType(Symbol.for('a'))).toBe('symbol'));
test('regexp', () => expect(getType(/abc/)).toBe('regexp'));

});
27 changes: 23 additions & 4 deletions packages/jest-matcher-utils/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,19 @@

'use strict';

import type {ValueType} from 'types/Values';
export type ValueType =
| 'array'
| 'boolean'
| 'function'
| 'null'
| 'number'
| 'object'
| 'regexp'
| 'string'
| 'symbol'
| 'undefined';

const chalk = require('chalk');

// get the type of a value with handling the edge cases like `typeof []`
// and `typeof null`
Expand All @@ -30,6 +42,9 @@ const getType = (value: any): ValueType => {
} else if (typeof value === 'string') {
return 'string';
} else if (typeof value === 'object') {
if (value.constructor === RegExp) {
return 'regexp';
}
return 'object';
// $FlowFixMe https://github.com/facebook/flow/issues/1015
} else if (typeof value === 'symbol') {
Expand Down Expand Up @@ -75,6 +90,9 @@ const stringify = (obj: any): string => {
});
};

// highlight an object
const h = obj => chalk.cyan.bold(stringify(obj));

const ensureNoExpected = (expected: any, matcherName: string) => {
matcherName || (matcherName = 'This');
if (typeof expected !== 'undefined') {
Expand Down Expand Up @@ -108,10 +126,11 @@ const ensureNumbers = (actual: any, expected: any, matcherName: string) => {
};

module.exports = {
getType,
stringify,
ensureNoExpected,
ensureActualIsNumber,
ensureExpectedIsNumber,
ensureNoExpected,
ensureNumbers,
getType,
h,
stringify,
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
exports[`.toThrowError() error class did not throw at all 1`] = `"Expected the function to throw an error of \"Err\" type, but it didn\'t."`;

exports[`.toThrowError() error class threw, but class did not match 1`] = `
"Expected the function to throw an error of \"Err2\" type, but it didn\'t.
Actual error:
type: \"Err\"
message: \"apple\""
`;

exports[`.toThrowError() error class threw, but should not have 1`] = `
"Expected the function to not throw an error of \"Err\" type, but it did.
Actual error:
type: \"Err\"
message: \"apple\""
`;

exports[`.toThrowError() regexp did not throw at all 1`] = `"Expected the function to throw an error matching \"/apple/\", but it didn\'t."`;

exports[`.toThrowError() regexp threw, but message did not match 1`] = `
"Expected the function to throw an error matching \"/banana/\", but it didn\'t.
Actual error:
type: \"Error\"
message: \"apple\""
`;

exports[`.toThrowError() regexp threw, but should not have 1`] = `
"Expected the function to not throw an error matching \"/apple/\", but it did.
Actual error:
type: \"Error\"
message: \"apple\""
`;

exports[`.toThrowError() strings did not throw at all 1`] = `"Expected the function to throw an error matching \"apple\", but it didn\'t."`;

exports[`.toThrowError() strings threw, but message did not match 1`] = `
"Expected the function to throw an error matching \"banana\", but it didn\'t.
Actual error:
type: \"Error\"
message: \"apple\""
`;

exports[`.toThrowError() strings threw, but should not have 1`] = `
"Expected the function to not throw an error matching \"apple\", but it did.
Actual error:
type: \"Error\"
message: \"apple\""
`;
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,7 @@ describe('.toHaveBeenCalledTimes()', () => {
it('accept only numbers', () => {
const foo = jasmine.createSpy('foo');
foo();

expect(() => jestExpect(foo).toHaveBeenCalledTimes(1))
.not.toThrowError();
jestExpect(foo).toHaveBeenCalledTimes(1);

[{}, [], true, 'a', new Map(), () => {}].forEach(value => {
expect(() => jestExpect(foo).toHaveBeenCalledTimes(value))
Expand Down
149 changes: 149 additions & 0 deletions packages/jest-matchers/src/__tests__/toThrowMatchers-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
/**
* Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @emails oncall+jsinfra
*/

'use strict';

describe('.toThrowError()', () => {
describe('strings', () => {
it('passes', () => {
expect(() => { throw new Error('apple'); }).toThrowError('apple');
expect(() => { throw new Error('banana'); }).not.toThrowError('apple');
expect(() => {}).not.toThrowError('apple');
});

test('did not throw at all', () => {
let error;
try {
expect(() => {}).toThrowError('apple');
} catch (e) {
error = e;
}

expect(error).toBeDefined();
expect(error.message).toMatchSnapshot();
});

test('threw, but message did not match', () => {
let error;
try {
expect(() => { throw new Error('apple'); }).toThrowError('banana');
} catch (e) {
error = e;
}

expect(error).toBeDefined();
expect(error.message).toMatchSnapshot();
});

test('threw, but should not have', () => {
let error;
try {
expect(() => { throw new Error('apple'); }).not.toThrowError('apple');
} catch (e) {
error = e;
}

expect(error).toBeDefined();
expect(error.message).toMatchSnapshot();
});
});

describe('regexp', () => {
it('passes', () => {
expect(() => { throw new Error('apple'); }).toThrowError(/apple/);
expect(() => { throw new Error('banana'); }).not.toThrowError(/apple/);
expect(() => {}).not.toThrowError(/apple/);
});

test('did not throw at all', () => {
let error;
try {
expect(() => {}).toThrowError(/apple/);
} catch (e) {
error = e;
}

expect(error).toBeDefined();
expect(error.message).toMatchSnapshot();
});

test('threw, but message did not match', () => {
let error;
try {
expect(() => { throw new Error('apple'); }).toThrowError(/banana/);
} catch (e) {
error = e;
}

expect(error).toBeDefined();
expect(error.message).toMatchSnapshot()
});

test('threw, but should not have', () => {
let error;
try {
expect(() => { throw new Error('apple'); }).not.toThrowError(/apple/);
} catch (e) {
error = e;
}

expect(error).toBeDefined();
expect(error.message).toMatchSnapshot();
});
});

describe('error class', () => {
class Err extends Error {}
class Err2 extends Error {}

it('passes', () => {
expect(() => { throw new Err(); }).toThrowError(Err);
expect(() => { throw new Err(); }).toThrowError(Error);
expect(() => { throw new Err(); }).not.toThrowError(Err2);
expect(() => {}).not.toThrowError(Err);
});

test('did not throw at all', () => {
let error;
try {
expect(() => {}).toThrowError(Err);
} catch (e) {
error = e;
}

expect(error).toBeDefined();
expect(error.message).toMatchSnapshot();
});

test('threw, but class did not match', () => {
let error;
try {
expect(() => { throw new Err('apple'); }).toThrowError(Err2);
} catch (e) {
error = e;
}

expect(error).toBeDefined();
expect(error.message).toMatchSnapshot();
});

test('threw, but should not have', () => {
let error;
try {
expect(() => { throw new Err('apple'); }).not.toThrowError(Err);
} catch (e) {
error = e;
}

expect(error).toBeDefined();
expect(error.message).toMatchSnapshot();
});
});
});
33 changes: 31 additions & 2 deletions packages/jest-matchers/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,15 @@ import type {
} from './types';

const matchers = require('./matchers');
const spyMatchers = require('./spy-matchers');
const spyMatchers = require('./spyMatchers');
const toThrowMatchers = require('./toThrowMatchers');

const {stringify} = require('jest-matcher-utils');

const GLOBAL_MATCHERS_OBJECT_SYMBOL = Symbol.for('$$jest-matchers-object');

class JestAssertionError extends Error {}

if (!global[GLOBAL_MATCHERS_OBJECT_SYMBOL]) {
Object.defineProperty(
global,
Expand Down Expand Up @@ -57,6 +63,8 @@ const makeThrowingMatcher = (
{args: arguments},
);

_validateResult(result);

if ((result.pass && isNot) || (!result.pass && !isNot)) { // XOR
let message = result.message;

Expand All @@ -66,7 +74,7 @@ const makeThrowingMatcher = (
message = message();
}

const error = new Error(message);
const error = new JestAssertionError(message);
// Remove this function from the stack trace frame.
Error.captureStackTrace(error, throwingMatcher);
throw error;
Expand All @@ -78,9 +86,30 @@ const addMatchers = (matchersObj: MatchersObject): void => {
Object.assign(global[GLOBAL_MATCHERS_OBJECT_SYMBOL], matchersObj);
};

const _validateResult = result => {
if (
typeof result !== 'object' ||
typeof result.pass !== 'boolean' ||
!(
typeof result.message === 'string' ||
typeof result.message === 'function'
)
) {
throw new Error(
'Unexpected return from a matcher function.\n' +
'Matcher functions should ' +
'return an object in the following format:\n' +
' {message: string | function, pass: boolean}\n' +
`'${stringify(result)}' was returned`,
);
}
};

// add default jest matchers
addMatchers(matchers);
addMatchers(spyMatchers);
addMatchers(toThrowMatchers);


module.exports = {
addMatchers,
Expand Down
File renamed without changes.
Loading

0 comments on commit 9641abf

Please sign in to comment.