format failed results better
This commit is contained in:
parent
3383718aac
commit
141999b881
|
@ -0,0 +1,23 @@
|
|||
# The MIT License (MIT)
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2021 Sean Sube
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -1,4 +1,9 @@
|
|||
{
|
||||
"name": "mocha-froth",
|
||||
"version": "0.1.0",
|
||||
"main": "out/src/index.js",
|
||||
"author": "ssube <seansube@gmail.com>",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@types/chai-as-promised": "^7.1.4",
|
||||
"@types/mocha": "^8.2.3",
|
||||
|
|
56
src/index.ts
56
src/index.ts
|
@ -1,31 +1,19 @@
|
|||
import { Arbitrary, check, property } from 'fast-check';
|
||||
import { Arbitrary, check, property, RunDetails, Parameters } from 'fast-check';
|
||||
|
||||
export type Check<T> = (this: Mocha.Context, t: T) => boolean;
|
||||
export type Check<T> = (this: Mocha.Context, val: T) => boolean;
|
||||
export type WrappedIt<T> = (name: string, check: Check<T>) => void;
|
||||
export type Suite<T> = (it: WrappedIt<T>) => void;
|
||||
|
||||
export function over<T>(name: string, strategy: Arbitrary<T>, suite: Suite<T>): void {
|
||||
export function over<T>(name: string, strategy: Arbitrary<T>, suite: Suite<T>, parameters?: Parameters<[T]>): void {
|
||||
describe(name, () => {
|
||||
suite((name, test) => {
|
||||
it(name, function (this: Mocha.Context): Promise<void> {
|
||||
const ctx = this;
|
||||
|
||||
return new Promise((res, rej) => {
|
||||
const result = check(property(strategy, (t) => test.call(ctx, t)));
|
||||
const result = check(property(strategy, (val) => test.call(ctx, val)), parameters);
|
||||
if (result.failed) {
|
||||
if (result.counterexample !== null) {
|
||||
const examples = result.counterexample.map(it => {
|
||||
if (typeof it === 'string') {
|
||||
return `'${it}'`;
|
||||
} else {
|
||||
return it;
|
||||
}
|
||||
}).join(',');
|
||||
const error = `failed after ${result.numRuns} runs and ${result.numShrinks} shrinks, failing on: ${examples}`;
|
||||
rej(new Error(error));
|
||||
} else {
|
||||
rej(new Error(`failed after ${result.numRuns} runs and ${result.numShrinks} shrinks, without counterexamples`));
|
||||
}
|
||||
rej(new Error(formatDetails(result)));
|
||||
} else {
|
||||
res();
|
||||
}
|
||||
|
@ -34,3 +22,37 @@ export function over<T>(name: string, strategy: Arbitrary<T>, suite: Suite<T>):
|
|||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function formatDetails<T>(details: RunDetails<[T]>): string {
|
||||
const prefix = formatPrefix(details);
|
||||
const counts = `${prefix} after ${details.numRuns} runs and ${details.numShrinks} shrinks`;
|
||||
|
||||
if (details.counterexample !== null) {
|
||||
const examples = details.counterexample.map((val) => {
|
||||
if (isString(val)) {
|
||||
return `'${val}'`;
|
||||
} else {
|
||||
return val;
|
||||
}
|
||||
}).join(',');
|
||||
return `${counts}, failing on: ${examples}`;
|
||||
} else {
|
||||
return `${counts}, without counterexamples`;
|
||||
}
|
||||
}
|
||||
|
||||
export function formatPrefix<T>(details: RunDetails<[T]>): string {
|
||||
if (isString(details.error)) {
|
||||
if (details.error.startsWith('Error:')) {
|
||||
return 'Property failed by throwing an error';
|
||||
}
|
||||
|
||||
return details.error;
|
||||
}
|
||||
|
||||
return 'Property failed without a reason';
|
||||
}
|
||||
|
||||
export function isString(val: unknown): val is string {
|
||||
return typeof val === 'string';
|
||||
}
|
||||
|
|
|
@ -12,12 +12,31 @@ describe('some foo', () => {
|
|||
it('should be even', (bar: number) => {
|
||||
return bar % 2 === 0;
|
||||
});
|
||||
|
||||
it('should not throw', (t: number) => {
|
||||
if (t.toString()[3] === '9') {
|
||||
throw new Error('not a real number!');
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
});
|
||||
|
||||
over('some IDs', uuid(), (it) => {
|
||||
beforeEach(() => {
|
||||
console.log('before each ID test');
|
||||
});
|
||||
|
||||
it('should be a good one', (id: string) => {
|
||||
return id[9] !== 'a';
|
||||
});
|
||||
|
||||
it('should be long enough', (id: string) => {
|
||||
return id.length > 2;
|
||||
});
|
||||
}, {
|
||||
examples: [['a']],
|
||||
numRuns: 1_000_000_000,
|
||||
});
|
||||
|
||||
over('even numbers', integer().filter(n => n % 2 === 0), (it) => {
|
||||
|
|
Loading…
Reference in New Issue