Tests
A Betterer test is a programmatic definition of something that you want to improve in your codebase. Typically this will either be something that is measured across the entire project (e.g. code coverage, or number of tests, or a specific performance metric), or something that is measured on a per-file basis (e.g. type checking, or linting).
Tests are defined as code and can be re-used and shared with other projects. Betterer even comes with a few built-in tests.
Basic test
The most basic test you can write checks if a number grows or shrinks:
- TypeScript
- JavaScript
// .betterer.ts
import { BettererTest } from '@betterer/betterer';
import { bigger, smaller } from '@betterer/constraints';
export default {
'should grow': () =>
new BettererTest({
test: () => getNumberOfTests(),
constraint: bigger
}),
'should shrink': () =>
new BettererTest({
test: () => getBundleSize(),
constraint: smaller
})
};
function getNumberOfTests(): number {
// ...
}
function getBundleSize(): number {
// ...
}
// .betterer.js
const { BettererTest } = require('@betterer/betterer');
const { bigger, smaller } = require('@betterer/constraints');
module.exports = {
'should grow': () =>
new BettererTest({
test: () => getNumberOfTests(),
constraint: bigger
}),
'should shrink': () =>
new BettererTest({
test: () => getBundleSize(),
constraint: smaller
})
};
function getNumberOfTests() {
// ...
}
function getBundleSize() {
// ...
}
Skipping and targeting tests
A BettererTest
has nice helpers for skipping tests and selecting specific tests to run.
- TypeScript
- JavaScript
// .betterer.ts
import { BettererTest } from '@betterer/betterer';
import { bigger, smaller } from '@betterer/constraints';
export default {
'should grow': () =>
new BettererTest({
test: () => getNumberOfTests(),
constraint: bigger
}).only(),
'should shrink': () =>
new BettererTest({
test: () => getBundleSize(),
constraint: smaller
}).skip()
};
function getNumberOfTests(): number {
// ...
}
function getBundleSize(): number {
// ...
}
// .betterer.js
const { BettererTest } = require('@betterer/betterer');
const { bigger, smaller } = require('@betterer/constraints');
module.exports = {
'should grow': () =>
new BettererTest({
test: () => getNumberOfTests(),
constraint: bigger
}).only(),
'should shrink': () =>
new BettererTest({
test: () => getBundleSize(),
constraint: smaller
}).skip()
};
function getNumberOfTests() {
// ...
}
function getBundleSize() {
// ...
}
Test goal
You can add a goal to your test, which can be a function or a value. Once the goal is met, the test will be marked as "completed":
- TypeScript
- JavaScript
// .betterer.ts
import { BettererTest } from '@betterer/betterer';
import { bigger, smaller } from '@betterer/constraints';
export default {
'should grow': () =>
new BettererTest({
test: () => getNumberOfTests(),
constraint: bigger,
goal: (value: number) => value > 1000
}),
'should shrink': () =>
new BettererTest({
test: () => getBundleSize(),
constraint: smaller,
goal: 5
})
};
function getNumberOfTests(): number {
// ...
}
function getBundleSize(): number {
// ...
}
// .betterer.js
const { BettererTest } = require('@betterer/betterer');
const { bigger, smaller } = require('@betterer/constraints');
module.exports = {
'should grow': () =>
new BettererTest({
test: () => getNumberOfTests(),
constraint: bigger,
goal: (value) => value > 1000
}),
'should shrink': () =>
new BettererTest({
test: () => getBundleSize(),
constraint: smaller,
goal: 5
})
};
function getNumberOfTests() {
// ...
}
function getBundleSize() {
// ...
}
Test deadline
You can add a deadline to your test, which can be a Date
object, or a valid Date
string. Once the deadline is passed, the test will be marked as "expired":
- TypeScript
- JavaScript
// .betterer.ts
import { BettererTest } from '@betterer/betterer';
import { bigger, smaller } from '@betterer/constraints';
export default {
'should grow': () =>
new BettererTest({
test: () => getNumberOfTests(),
constraint: bigger,
deadline: new Date('2021/07/03')
}),
'should shrink': () =>
new BettererTest({
test: () => getBundleSize(),
constraint: smaller,
deadline: '2021/07/03'
})
};
function getNumberOfTests(): number {
// ...
}
function getBundleSize(): number {
// ...
}
// .betterer.js
const { BettererTest } = require('@betterer/betterer');
const { bigger, smaller } = require('@betterer/constraints');
module.exports = {
'should grow': () =>
new BettererTest({
test: () => getNumberOfTests(),
constraint: bigger,
deadline: new Date('2021/07/03')
}),
'should shrink': () =>
new BettererTest({
test: () => getBundleSize(),
constraint: smaller,
deadline: '2021/07/03'
})
};
function getNumberOfTests() {
// ...
}
function getBundleSize() {
// ...
}
File test
If you want to write a test that checks individual files, you can write a BettererFileTest
:
- TypeScript
- JavaScript
// .betterer.ts
import { BettererFileTest } from '@betterer/betterer';
export default {
'no more JavaScript files': () => countFiles('no more JavaScript files!').include('**/*.js')
};
function countFiles(issue: string) {
return new BettererFileTest(async (filePaths, fileTestResult) => {
filePaths.forEach((filePath) => {
// In this case the file contents don't matter:
const file = fileTestResult.addFile(filePath, '');
// Add the issue to the first character of the file:
file.addIssue(0, 0, issue);
});
});
}
// .betterer.js
const { BettererFileTest } = require('@betterer/betterer');
module.exports = {
'no more JavaScript': () => countFiles('no more JavaScript files!').include('**/*.js')
};
function countFiles(issue) {
return new BettererFileTest(async (filePaths, fileTestResult) => {
filePaths.forEach((filePath) => {
// In this case the file contents don't matter:
const file = fileTestResult.addFile(filePath, '');
// Add the issue to the first character of the file:
file.addIssue(0, 0, issue);
});
});
}
Complex test
If you want to do more fancy custom things, you can have complete control over constraints, diffing, serialising/deserialising and printing.
- TypeScript
- JavaScript
// .betterer.ts
import { BettererTest } from '@betterer/betterer';
import { BettererConstraintResult } from '@betterer/constraints';
type AccessibilityReport = {
warnings: Array<string>;
errors: Array<string>;
};
export default {
'should be accessible': () =>
new BettererTest<AccessibilityReport>({
test: accessibilityTest,
constraint: accessibilityConstraint
})
};
function accessibilityTest(): AccessibilityReport {
// ...
}
function accessibilityConstraint(result: AccessibilityReport, expected: AccessibilityReport): BettererConstraintResult {
if (result.errors > expected.errors || result.warnings > expected.warnings) {
return BettererConstraintResult.worse;
}
if (result.errors < expected.errors || result.warnings < expected.warnings) {
return BettererConstraintResult.better;
}
return BettererConstraintResult.same;
}
// .betterer.js
const { BettererTest } = require('@betterer/betterer');
const { BettererConstraintResult } = require('@betterer/constraints');
module.exports = {
'should be accessible': () =>
new BettererTest({
test: accessibilityTest,
constraint: accessibilityConstraint
})
};
function accessibilityTest() {
// ...
}
function accessibilityConstraint(result, expected) {
if (result.errors > expected.errors || result.warnings > expected.warnings) {
return BettererConstraintResult.worse;
}
if (result.errors < expected.errors || result.warnings < expected.warnings) {
return BettererConstraintResult.better;
}
return BettererConstraintResult.same;
}