Skip to content

Commit

Permalink
.toThrowError()
Browse files Browse the repository at this point in the history
  • Loading branch information
aaronabramov committed Jul 15, 2016
1 parent 99f6fa7 commit e3e9c3a
Show file tree
Hide file tree
Showing 8 changed files with 285 additions and 24 deletions.
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'));

});
15 changes: 14 additions & 1 deletion packages/jest-matcher-utils/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,17 @@

'use strict';

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

// get the type of a value with handling the edge cases like `typeof []`
// and `typeof null`
Expand All @@ -30,6 +40,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
161 changes: 161 additions & 0 deletions packages/jest-matchers/src/__tests__/toThrowMatchers-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
/**
* 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('lol'); }).toThrowError('lol');
expect(() => { throw new Error('lmao'); }).not.toThrowError('lol');
expect(() => {}).not.toThrowError('lol');
});

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

expect(error).toBeDefined();
expect(error.message).toMatch(
/Expected.*matching.*lol.*but it didn't\.$/,
);
});

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

expect(error).toBeDefined();
expect(error.message).toMatch(
/Expected.*matching.*\nActual error.*\n.*Error.*\n.*lol/,
);
});

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

expect(error).toBeDefined();
expect(error.message).toMatch(/but it did\./);
});
});

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

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

expect(error).toBeDefined();
expect(error.message).toMatch(
/Expected.*matching.*lol.*but it didn't\.$/,
);
});

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

expect(error).toBeDefined();
expect(error.message).toMatch(
/Expected.*matching.*\nActual error.*\n.*Error.*\n.*lol/,
);
});

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

expect(error).toBeDefined();
expect(error.message).toMatch(/but it did\./);
});
});

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).toMatch(
/Expected.*of.*Err.*class.*but it didn't\.$/,
);
});

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

expect(error).toBeDefined();
expect(error.message).toMatch(
/Expected.*of.*Err.*class.*\nActual error.*\n.*Err.*\n.*lol/,
);
});

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

expect(error).toBeDefined();
expect(error.message).toMatch(/but it did\./);
});
});
});
26 changes: 25 additions & 1 deletion packages/jest-matchers/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,11 @@ 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');

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

_validateResult(result);

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

Expand All @@ -78,9 +84,27 @@ 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.
83 changes: 83 additions & 0 deletions packages/jest-matchers/src/toThrowMatchers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/**
* Copyright (c) 2014, 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.
*
* @flow
*/

'use strict';

import type {MatchersObject} from './types';

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

const matchers: MatchersObject = {
toThrowError(actual: Function, expected: string | Error | RegExp) {
let error;

try {
actual();
} catch (e) {
error = e;
}

if (typeof expected === 'string') {
return _toThrowMatchingStringOrRegexp(error, expected);
} else if (typeof expected === 'function') {
return _toThrowMatchingError(error, expected);
} else if (expected instanceof RegExp) {
return _toThrowMatchingStringOrRegexp(error, expected);
} else {
throw new Error(
'Unexpected argument passed. Expected to get ' +
`'string', 'Error class' or 'regexp'. Got: '${getType(expected)}' ` +
`'${stringify(expected)}'`,
);
}
},
};

const _toThrowMatchingStringOrRegexp = (
error,
strOrRegExp: string | RegExp,
) => {
const pass = !!(error && error.message.match(strOrRegExp));
let message = pass
? 'Expected the function to not throw error matching ' +
`'${stringify(strOrRegExp)}', but it did.`
: 'Expected the function to throw error matching ' +
`'${stringify(strOrRegExp)}', but it didn't.`;

if (error) {
message += _printThrownError(error);
}

return {pass, message};
};

const _toThrowMatchingError = (error, ErrorClass) => {
const pass = !!(error && error instanceof ErrorClass);
let message = pass
? `Expected the function to not throw error of '${ErrorClass.name}' ` +
'class, but it did.'
: `Expected the function to throw error of '${ErrorClass.name}' ` +
`class, but it didn't.`;

if (error) {
message += _printThrownError(error);
}

return {pass, message};
};

const _printThrownError = error => {
return '\n' +
'Actual error:\n' +
`class: '${error.constructor.name}'\n` +
`message: '${error.message}'`;
};

module.exports = matchers;
22 changes: 0 additions & 22 deletions types/Values.js

This file was deleted.

0 comments on commit e3e9c3a

Please sign in to comment.