TestCheck.js

Generative testing for JavaScript.

API Documentation

The testcheck npm module exports six values:

const {
  check,
  property,
  sample,
  sampleOne,
  gen,
  ValueGenerator
} = require('testcheck')

Try it! Open the developer console to take the API for a test run.

Running Tests

check()

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])

Returns

An Object with the properties:

property()

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)

Returns

A Property to be used by check().

sample()

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])

By default 10 samples are provided unless otherwise specified.

Returns

An Array of values from generator.

sampleOne()

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])

Returns

A single value from generator.

Generator Builders

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.

gen()

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:

Parameters

gen(valueShape)

Primitive Value Generators

gen.any

Generates any JS value, including Arrays and Objects (possibly nested).

gen.primitive

Generates any primitive JS value: strings, numbers, booleans, null, undefined, or NaN.

gen.boolean

Generates true or false values.

gen.null

Generates only the value null.

gen.undefined

Generates only the value undefined.

gen.NaN

Generates only the value NaN.

Number Generators

gen.number

Generates floating point numbers (including +Infinity, -Infinity, and NaN).

gen.posNumber

Generates only positive numbers (0 though +Infinity), does not generate NaN.

gen.negNumber

Generates only negative numbers (0 though -Infinity), does not generate NaN.

gen.numberWithin()

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)

gen.int

Generates integers (32-bit signed) including negative numbers and 0.

gen.posInt

Generates positive integers, including 0.

gen.negInt

Generates negative integers, including 0.

gen.sPosInt

Generates only strictly positive integers, not including 0.

gen.sNegInt

Generates only strictly negative integers, not including 0.

gen.intWithin()

Generates an integer within the provided (inclusive) range.

Note: The resulting ValueGenerator is not shrinkable.

Parameters

gen.intWithin(min, max)

String Generators

gen.string

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.

gen.asciiString

Generates strings of printable ascii characters.

sample(gen.asciiString)
// [ '', 'j', ':o', 'EM5', 'I]', '', 'GCeTvG', '\'\\zB+', '8gc7y', 'g3Ei' ]

gen.alphaNumString

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' ]

gen.substring()

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)

gen.char

Generates arbitrary 1-byte characters (code 0 through 255).

sample(gen.char)
// [ 'ã', '}', '£', 'O', '\u000b', '±', '\n', '\u0007', 'ÿ', 'b' ]

gen.asciiChar

Generates only printable ascii characters (code 32 through 126).

sample(gen.asciiChar)
// [ 'q', '-', '8', 'I', 'O', ';', 'A', 'm', '3', '9' ]

gen.alphaNumChar

Generates only alpha-numeric characters: a-z, A-Z, 0-9.

sample(gen.alphaNumChar)
// [ 'x', '8', 'T', '9', '5', 'w', 'U', 'a', 'J', 'f' ]

Collection Generators

gen.array()

Generates Arrays of values. There are a few forms gen.array can be used:

Parameters

gen.array(valueGen[, options])

gen.uniqueArray()

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])

gen.object()

Generates Objects of values. There are a few forms gen.object can be used:

Parameters

gen.object([keyGen, ]valueGen[, options])

gen.arrayOrObject()

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)

gen.nested()

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)

JSON Generators

gen.JSON

Generates JSON Objects where each key is a JSON value.

gen.JSONValue

Generates JSON values: primitives, or (possibly nested) arrays or objects.

gen.JSONPrimitive

Generates JSON primitives: strings, numbers, booleans and null.

Generator Creators

gen.oneOf()

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)

gen.oneOfWeighted()

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)

gen.return()

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)

gen.deepCopyOf()

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)

gen.sized()

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)

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()

ValueGenerator#nullable()

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.

ValueGenerator#notEmpty()

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.

ValueGenerator#suchThat()

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)

Returns

A new ValueGenerator.

ValueGenerator#then()

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)

Returns

A new ValueGenerator.

ValueGenerator#scale()

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)

Returns

A new ValueGenerator.

ValueGenerator#neverShrink()

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.

ValueGenerator#alwaysShrink()

Creates a new ValueGenerator which will always consider shrinking, even if the property passes (up to one additional level).

Returns

A new ValueGenerator.