Generative testing for JavaScript.
The testcheck
npm module exports six values:
const {
check,
property,
sample,
sampleOne,
gen,
ValueGenerator
} = require('testcheck')
check
: Runs a property test.property
: Defines a property test.sample
& sampleOne
: Samples generator values for debugging.gen
: A collection of ValueGenerators and functions that return ValueGenerators.ValueGenerator
: The class which all ValueGenerators are instances of.Try it! Open the developer console to take the API for a test run.
Given a property to check, return the result of the check.
If the property generates a false value, check will shrink the generator
and return a Result which includes the shrunk
key.
const { check, property, gen } = require('testcheck')
check(property(gen.int, n => n - n === 0), { numTests: 1000 })
// { result: true, seed: 1482203464997, numTests: 1000 }
Parameters
check(property[, options])
property
: A Property created via the property()
function.
options
: An optional Object of options with the properties:
numTests
: Number of times to check the property. Default: 100
maxSize
: The maximum “size” to provide to generators. Default: 200
seed
: The seed to use for the random number generator. Default: *
Returns
An Object with the properties:
result
: true
if the check passed, otherwise false
or any Error thrown.
numTests
: The number of times the property
was tested with generated values.
seed
: The random number seed used for this check, pass this seed within options
to get the exact same tests.
fail
: The arguments property
generated when and if this check failed.
failingSize
: The size used when and if this check failed.
shrunk
: When a check fails, the failing arguments shrink to find the
smallest value that fails, resulting in an Object with properties:
smallest
: The smallest arguments with this result.
result
: true
if the check passed, otherwise false
or any Error thrown.
depth
: The depth of the shrunk result.
totalNodesVisited
: The number of nodes shrunk to result in this smallest failing value.
Creates a Property as needed by check()
.
Accepts any number of value generators, the results of which become the
arguments of the property function. The property function should return
true
if the property is upheld, or false
if it fails.
const numGoUp = property(gen.int, gen.posInt, (a, b) => a + b > a);
check(numGoUp);
Property functions may also throw an Error when a property fails, which is helpful for testing code that uses assertions or testing with checking libraries such as chai.
const { expect } = require('chai')
const allIntsArePositive = property(gen.int, num => {
expect(num).to.be.at.least(0);
})
const test = check(allIntsArePositive)
console.log(test.shrunk.result)
// AssertionError: expected -1 to be at least 0
Parameters
property(gen[, gen2[, ...genN]], propertyFn)
gen
: Any ValueGenerator instance. Values from which will be provided as arguments to propertyFn
. Multiple ValueGenerators may be provided, each of which will produce another function argument.
propertyFn
: Function representing a property that should always be true. Returns true
when the property is upheld and false
when the property fails.
Returns
A Property to be used by check()
.
Handy tool for visualizing the output of a ValueGenerator.
Given a ValueGenerator, it returns an Array of values resulting from the generator.
sample(gen.int)
// [ 0, 1, 1, 2, 3, 3, -6, 1, -3, -8 ]
Note: Whenever a non-ValueGenerator is provided to a function which expects
a ValueGenerator, like sample()
, it is converted to a ValueGenerator with gen()
.
sample([ gen.int, gen.alphaNumChar ], 3)
// [ [ 0, 'a' ], [ -1, 'M' ], [ 1, 'm' ] ]
Parameters
sample(generator[, numValues])
generator
: Any ValueGenerator object.
numValues
: The number of values to produce. Default: 10
.
By default 10 samples are provided unless otherwise specified.
Returns
An Array of values from generator
.
Handy tool for visualizing the output of your ValueGenerator.
Given a ValueGenerator, it returns a single value generated for a given size
.
sampleOne(gen.int)
// 24
Note: Whenever a non-ValueGenerator is provided to a function which expects
a ValueGenerator, like sampleOne()
, it is converted to a ValueGenerator with gen()
.
sampleOne([ gen.int, gen.alphaNumChar ])
// [ 8, 'j' ]
Parameters
sample(generator[, size])
generator
: Any ValueGenerator object.
size
: The size of the value to produce. Default: 30
.
Returns
A single value from generator
.
Generators are responsible for generating test values for use within property tests. Generators can produce simple values like numbers, booleans, or strings, as well as more complex values like Arrays and Objects. The API below list ready-made generators to reference as well as functions that build new generators.
Generates a specific shape of values given an initial nested Array or Object which
contain Generators. Any values within the provided shape which don’t contain
generators will be copied (with gen.deepCopyOf()
).
Note: Whenever a non-ValueGenerator is provided to a function which expects a ValueGenerator,
it is converted to a ValueGenerator with gen()
. That makes calling this function
optional for most cases, unless trying to be explicit or when using TypeScript or Flow.
There are a few forms gen()
can be used:
Generate an Array shape with different values at each index (also known as “tuples”)
For example, a tuples of [ “constant”, int, bool ] like ['foo', 3, true]
:
gen([ 'foo', gen.int, gen.boolean ])
Generate an Object shape with different values for each key (also known as “records”)
For example, a record of { x: “constant”, y: int, z: bool } like { x: 'foo', y: -4, z: false }
:
gen({ x: 'foo', y: gen.int, z: gen.boolean })
Combinations of Array and Object shapes with generators at any point:
For example, a data shape for a complex “place” data shape might look like:
gen({
type: 'Place',
name: gen.string,
location: [ gen.number, gen.number ],
address: {
street: gen.string,
city: gen.string
}
})
Parameters
gen(valueShape)
valueShape
: A value, object, or array, which may nest other values, objects and
arrays, which at any point may contain a ValueGenerator used to produce final values.Generates any JS value, including Arrays and Objects (possibly nested).
Generates any primitive JS value: strings, numbers, booleans, null
, undefined
, or NaN
.
Generates true
or false
values.
Generates only the value null
.
Generates only the value undefined
.
Generates only the value NaN
.
Generates floating point numbers (including +Infinity
, -Infinity
, and NaN
).
Generates only positive numbers (0
though +Infinity
), does not generate NaN
.
Generates only negative numbers (0
though -Infinity
), does not generate NaN
.
Generates a floating point number within the provided (inclusive) range.
Does not generate NaN
or Infinity
.
Note: The resulting ValueGenerator is not shrinkable.
Parameters
gen.numberWithin(min, max)
min
The smallest possible number to generate (inclusive).
max
The largest possible number to generate (inclusive).
Generates integers (32-bit signed) including negative numbers and 0
.
Generates positive integers, including 0.
Generates negative integers, including 0.
Generates only strictly positive integers, not including 0.
Generates only strictly negative integers, not including 0.
Generates an integer within the provided (inclusive) range.
Note: The resulting ValueGenerator is not shrinkable.
Parameters
gen.intWithin(min, max)
min
The smallest possible integer to generate (inclusive).
max
The largest possible integer to generate (inclusive).
Generates strings of arbitrary characters.
sample(gen.string)
// [ '', 'c', '¸Ã', 'uq', 'd.', '', 'FÏs', 'Ú\u0019oÞ', 'Ô', 'ßÞ' ]
Note: strings of arbitrary characters may result in higher-plane Unicode characters and non-printable characters.
Generates strings of printable ascii characters.
sample(gen.asciiString)
// [ '', 'j', ':o', 'EM5', 'I]', '', 'GCeTvG', '\'\\zB+', '8gc7y', 'g3Ei' ]
Generates strings of only alpha-numeric characters: a-z, A-Z, 0-9.
sample(gen.alphaNumString)
// [ '', 'N', 'T', 's9', 'wm', 'eT', '9', 'lNu', 'h', '81EvZX' ]
Generates substrings of an original string (including the empty string).
sample(gen.substring('abracadabra'))
// [ 'ac', 'r', 'abra', 'braca', 'a', 'ad', 'b', 'r', '', 'abra' ]
Parameters
gen.substring(original)
original
The original string from which to generate substrings.Generates arbitrary 1-byte characters (code 0 through 255).
sample(gen.char)
// [ 'ã', '}', '£', 'O', '\u000b', '±', '\n', '\u0007', 'ÿ', 'b' ]
Generates only printable ascii characters (code 32 through 126).
sample(gen.asciiChar)
// [ 'q', '-', '8', 'I', 'O', ';', 'A', 'm', '3', '9' ]
Generates only alpha-numeric characters: a-z, A-Z, 0-9.
sample(gen.alphaNumChar)
// [ 'x', '8', 'T', '9', '5', 'w', 'U', 'a', 'J', 'f' ]
Generates Arrays of values. There are a few forms gen.array
can be used:
Generate Arrays of random sizes (ex. arrays of integers).
gen.array(gen.int)
Generate Arrays of specific sizes (ex. length of 5).
gen.array(gen.int, { size: 5 })
Generate Arrays of random sizes within a specific range (ex. between 2 and 10).
gen.array(gen.int, { minSize: 2, maxSize: 10 })
Parameters
gen.array(valueGen[, options])
valueGen
: A ValueGenerator which will produce the values of the resulting Arrays.
options
: An optional object of options describing the size of the resulting Arrays:
size
: If provided, the exact size of the resulting Array.
minSize
: If provided, the minimum size of the resulting Array.
maxSize
: If provided, the maximum size of the resulting Array.
Generates Arrays of unique values.
Accepts the same size options as gen.array()
For example, to generate an array of unique integers:
var genUniqueInts = gen.uniqueArray(gen.int)
sampleOne(genUniqueInts)
// [ 7, -9, 8, 14, 0, 19, 24, -31, 1, -13, 5 ]
Generated plain Objects and Arrays are deeply compared by value. For example, to generate an array of unique [x, y] points:
var genUniquePoints = gen.uniqueArray([ gen.int, gen.int ])
sampleOne(genUniquePoints)
// [ [ -9, -3 ], [ -1, -1 ], [ 2, -2 ], [ -11, -6 ], [ 9, -4 ] ]
Also optionally accepts a function to determine how to determine if more complex values are unique by translating them to a simpler value. For example, when generating Dates:
var genDate = gen.posInt.then(ms => new Date(ms))
var genUniqueDates = gen.uniqueArray(genDate, date => date.toString())
Parameters
gen.uniqueArray(valueGen[, uniqueFn][, options])
valueGen
: A ValueGenerator which will produce the values of the resulting Arrays.
uniqueFn
: A Function which accepts a value from valueGen
and returns a scalar value
which can be compared with ===
to determine uniqueness.
options
: An optional object of options describing the size of the resulting Arrays:
size
: If provided, the exact size of the resulting Array.
minSize
: If provided, the minimum size of the resulting Array.
maxSize
: If provided, the maximum size of the resulting Array.
Generates Objects of values. There are a few forms gen.object
can be used:
Generate Objects with a specified kind of value and alpha-numeric keys.
gen.object(gen.int)
Generate Objects of a specific size
gen.object(gen.int, { size: 5 })
Generate Objects with a specified kind of key and value, (ex. numeric keys).
gen.object(gen.int, gen.int)
Parameters
gen.object([keyGen, ]valueGen[, options])
keyGen
: An optional ValueGenerator which will produce the keys of the resulting Objects.
valueGen
: A ValueGenerator which will produce the values of the resulting Objects.
options
: An optional object of options describing the size of the resulting Objects:
size
: If provided, the exact size of the resulting Object.
minSize
: If provided, the minimum size of the resulting Object.
maxSize
: If provided, the maximum size of the resulting Object.
Generates either an Array or an Object with values of the provided kind.
sample(gen.arrayOrObject(gen.int), 5)
// [ [], {}, [ 1 ], { y96: -1, hfR: 1 }, [ -3, -7, 7, -5, 5 ] ]
Note: Objects will be produced with alpha-numeric keys.
Parameters
gen.arrayOrObject(valueGen)
valueGen
: A ValueGenerator which will produce the values of the resulting Arrays or Objects.Given a function which takes a ValueGenerator and returns a ValueGenerator (such as
gen.array
or gen.object
), and a ValueGenerator to use as values, creates
potentially nested values.
const deepArrayOfInts = gen.nested(gen.array, gen.int)
sampleOne(deepArrayOfInts)
// [ 0, [ -2 ], 1, [] ]
Note: It may generate just values, not wrapped in a container.
Parameters
gen.nested(collectionGenFn, valueGen)
collectionGenFn
: A Function which accepts a ValueGenerator (like valueGen
) and returns a new ValueGenerator which presumably generates collections that contain the provided ValueGenerator.
valueGen
: A ValueGenerator which will produce the values within the resulting collections.
Generates JSON Objects where each key is a JSON value.
Generates JSON values: primitives, or (possibly nested) arrays or objects.
Generates JSON primitives: strings, numbers, booleans and null
.
Creates a ValueGenerator which will generate one of the provided values or values from one of the provided ValueGenerators.
const numOrBool = gen.oneOf([ gen.int, gen.boolean ])
sample(numOrBool)
// [ false, true, 0, -3, -2, false, -3, false, 6, 8 ]
In addition to ValueGenerators, you can also provide normal values to gen.oneOf()
,
for example, picking one value from an Array of values:
const colors = [ 'Red', 'Orange', 'Yellow', 'Green', 'Blue', 'Indigo', 'Violet' ]
const genColors = gen.oneOf(colors)
sample(genColors, 5)
// [ 'Red', 'Blue', 'Blue', 'Violet', 'Red' ]
Parameters
gen.oneOf(arrayOfGens)
arrayOfGens
: An Array which contains either a ValueGenerator or value at each index.Similar to gen.oneOf()
, except provides probablistic “weights” to each generator.
const numOrRarelyBool = gen.oneOfWeighted([[10, gen.int], [1, gen.boolean]])
sample(numOrRarelyBool)
// [ 0, 0, false, -1, 0, -3, -4, -7, 4, -4 ]
Parameters
gen.oneOfWeighted(arrayOfWeightsAndGens)
arrayOfWeightsAndGens
: An Array of “tuples” (two-sized Arrays):
[ weight, valueGen ]
weight
: A number to determine how frequent this selection is relative
to other selections.
valueGen
: A ValueGenerator or value to use should this selection be chosen.
Creates a ValueGenerator which will always generate the provided value.
This is used rarely since almost everywhere a ValueGenerator can be accepted, a
regular value can be accepted as well. However regular values provided in those
cases will be copied. You may wish to use gen.return()
if you explicitly want
a reference to a value rather than a deep copy of that value.
const alwaysBlue = gen.return('blue');
sample(alwaysBlue)
[ 'blue', 'blue', 'blue', 'blue', 'blue', 'blue', 'blue', 'blue', 'blue', 'blue' ]
Parameters
gen.return(value)
value
: The value to always generate references of.Creates a ValueGenerator which will always generate a deep copy of the provided value.
This is used rarely since almost everywhere a ValueGenerator can be accepted,
a regular value can be accepted as well, which implicitly is converted to a
ValueGenerator using gen()
which itself calls gen.deepCopyOf()
for
non-generator values. However you may wish to use gen.deepCopyOf()
directly to either be explicit, resolve an ambiguity, or cause actual instances
of ValueGenerator to appear in test cases rather than their values.
Note that deep copy only copies plain Objects and Arrays, where instances of
other types, such as Date
, remain references. For different behavior, use:
gen.return(value).then(yourCustomCopyFn)
.
const threeThings = gen.deepCopyOf([1,2,3]);
const aValue = sampleOne(threeThings)
[ 1, 2, 3 ]
aValue.push(4);
[ 1, 2, 3, 4 ]
const anotherValue = sampleOne(threeThings)
[ 1, 2, 3 ]
Parameters
gen.deepCopyOf(value)
value
: The value to always generate deep copies of.Creates a ValueGenerator that relies on a size
. Size allows for the “shrinking”
of ValueGenerators. A larger “size” should result in a larger generated value.
Typically gen.sized()
is not used directly in a test, but may be used when
building custom ValueGenerators. Many of the ValueGenerators in this library
are built with gen.sized()
.
For example, gen.int
is shrinkable because it is implemented as:
gen.int = gen.sized(size => gen.intWithin(-size, size))
Parameters
gen.sized(genFn)
genFn
: A Function which accepts a size
and returns a ValueGenerator.A ValueGenerator instance produces values of a particular kind. ValueGenerators
cannot be constructed directly, but instead are obtained by one of the gen
values
or functions described above, or as the result of calling one of the prototype
methods of another ValueGenerator object.
// A generator of integers
const genInt = gen.int
// A generator of arrays of integers
const genIntArray = gen.array(gen.int)
// A generator of non-empty arrays of integers
const genNonEmptyIntArray = gen.array(gen.int).notEmpty()
Creates a new ValueGenerator which also sometimes generates null
values.
// A generator of integers or nulls.
const genNullableInt = gen.int.nullable()
sample(genNullableInt)
// [ 0, -1, null, null, 1, 4, 3, -3, -5, null ]
Returns
A new ValueGenerator.
Creates a new ValueGenerator which generates non-empty values.
Examples of empty values are 0
, ""
, null
, []
, and {}
const notEmptyStrings = gen.asciiString.notEmpty()
sample(notEmptyStrings, 5)
// [ 'f', 'SJ', '8?sH{', 'zWUb}X1', '.AS Mz.x7' ]
Returns
A new ValueGenerator.
Creates a new ValueGenerator which ensures that all values generated adhere to the given predicate function.
For example, to create a ValueGenerator of any number except multiples of 5:
var genAnythingBut5s = gen.int.suchThat(n => n % 5 !== 0);
sample(genAnythingBut5s)
// [ 0, 1, -1, 2, 4, -3, 6, 2, 4, -7 ]
Note: Care is needed to ensure there is a high chance the predicate will pass. After ten attempts an exception will throw.
Parameters
g.suchThat(predicateFn)
predicateFn
A function which accepts a value
from the ValueGenerator and
returns true
if it is allowed, or false
if not.Returns
A new ValueGenerator.
Creates a new ValueGenerator that depends on the values of this ValueGenerator.
For example, to create a ValueGenerator of square numbers:
var genSquares = gen.int.then(n => n * n);
sample(genSquares)
// [ 0, 0, 4, 9, 1, 16, 0, 36, 25, 81 ]
For example, to create a ValueGenerator which first generates an Array of integers, and then returns both that Array and a sampled value from it:
var genList = gen.array(gen.int).notEmpty();
var genListAndItem = genList.then(
list => [ list, gen.oneOf(list) ]
);
sample(genListAndItem, 3)
// [ [ [ 1 ], 1 ], [ [ 2, -1 ], 2 ], [ [ -3, 2, -1 ], 2 ] ]
Parameters
g.then(mappingFn)
mappingFn
A function which accepts a value
from the ValueGenerator and
returns either a new value, or a new ValueGenerator.Returns
A new ValueGenerator.
Creates a new ValueGenerator which grows at a different scale.
ValueGenerators start by producing very “small” values (closer to 0) at first, and produce larger values in later iterations of a test as a result of a “size” value which grows with each generation. Typically “size” grows linearly, but .scale() can alter a size to grow at different rates.
For example, to generate “big” numbers that grow super-linearly (cubicly):
var bigInts = gen.int.scale(n => n * n * n)
sample(bigInts)
// [ 0, 1, 5, 0, -59, -56, -160, 261, 409, -34 ]
Note: When shrinking a failing test, “size” gets smaller. If the scale function returns a value that’s not dependent on it’s input, then the resulting ValueGenerator will not shrink.
Parameters
g.scale(sizingFn)
sizingFn
A function which accepts a size
number and returns a new size.Returns
A new ValueGenerator.
Creates a new ValueGenerator which will never shrink. This is useful when shrinking is taking a long time or is not applicable.
Returns
A new ValueGenerator.
Creates a new ValueGenerator which will always consider shrinking, even if the property passes (up to one additional level).
Returns
A new ValueGenerator.