Need help with gentest?
Click the “chat” button below for chat support from the developer who created it, or find similar developers for support.

#### About the developer

graue
408 Stars 12 Forks MIT License 45 Commits 10 Opened issues

#### Description

Generative testing for JavaScript. Save time and catch more bugs by letting the computer write test cases for you. WIP

!
?

# 278,802
HTML
Clojure
Shell
server-...
45 commits

# gentest

Property-based, generative testing for JavaScript.

Don't handwrite unit tests. Save time and catch more bugs by writing properties, and let the computer generate test cases for you!

(This is a work in progress. Consider it "Stability 1: Experimental" for the time being. Feedback welcome.)

## Basic example

Let's say we want to test this add function:

```function add(x, y) {
return x + y;
}
```

We can begin by asking, "What properties should this function have?" One property is that it's commutative;

`add(x, y)`
should equal
`add(y, x)`
for any integers x and y. To test this, we could write a function that accepts a particular pair of values for x and y, and returns true if the property holds for those inputs:
```var ourProperty = function(x, y) {
};
```

Such a function is called a property in Gentest, but we're not quite finished. We also need to tell Gentest what

`x`
and
`y`
are so it can generate sample values. For now, let's restrict our input domain to integers, which we can create using the
`gentest.types.int`
generator.
```var t = gentest.types;

forAll([t.int, t.int], 'addition is commutative', function(x, y) {
});
```

We now have a complete example and can run the tests using the

`gentest`
executable.
`npm install -g gentest`
, then run
`gentest`
with your test file as an argument.

## Concepts and terms

A property is a parameterized test: a function that takes any number of arguments and returns a boolean, together with a description of how to generate that function's arguments.

A test is a particular test case, that is, a set of arguments to a property.

## API

### gentest.sample(type, [count])

Generates sample values of the given type.

### gentest.types

Contains the following type definitions, with built-in generators:

• `int`
• `int.nonNegative`
• `int.nonZero`
• `int.positive`
• `char`
• `string`
• `bool`
```gentest.sample(gentest.types.int);
// -> [ 0, 0, -1, 1, 0, 2, -2, 1, -2, -4 ]

gentest.sample(gentest.types.string);
// -> [ '', '', '', 'V', 'N', '{C', '(P', 'jb', 'I{=y', 'Ss' ]
```

And these higher-order type definitions:

#### arrayOf(type)

Produces arrays of the argument type.

```gentest.sample(gentest.types.arrayOf(gentest.types.bool));
// ->
// [ [],
//   [],
//   [ false ],
//   [ false ],
//   [ false ],
//   [ false ],
//   [ false, true, true ],
//   [ true, true, true ],
//   [ false,
//     false,
//     true,
//     true ],
//   [] ]
```

#### tuple(types)

Produces arrays that have one each of the given types, in order.

```var t = gentest.types;
gentest.sample(t.tuple([t.int, t.int, t.bool, t.string]))
// ->
// [ [ -1, -1, true, '' ],
//   [ 0, 0, true, 'B' ],
//   [ 2, 1, true, '!B' ],
//   [ 0, 0, true, '' ],
//   [ 2, 2, false, '\'D' ],
//   [ 2, 2, true, '@+' ],
//   [ 3, 1, true, '7gR]' ],
//   [ -2, 0, true, 'Z' ],
//   [ 0, -4, false, 'rr\$:' ],
//   [ 5, 4, true, '' ] ]
```

#### oneOf(types)

Produces any of the given types.

```gentest.sample(gentest.types.oneOf([gentest.types.bool, gentest.types.int]));
// ->
// [ 0,
//   true,
//   1,
//   false,
//   true,
//   true,
//   true,
//   -1,
//   0,
//   -4 ]
```

#### constantly(x)

Returns a generator that always yields the constant value

`x`
.

#### elements(elems)

Any of the given elements.

```var foods = gentest.types.elements(['pizza', 'chocolate', 'sushi']);
gentest.sample(foods);
// ->
// [ 'sushi',
//   'pizza',
//   'pizza',
//   'chocolate',
//   'sushi',
//   'pizza',
//   'chocolate',
//   'chocolate',
//   'chocolate',
//   'sushi' ]
```

#### shape(object)

Produces objects, with each key mapped to a value of the respective type.

```var person = gentest.types.shape({
name: gentest.types.string,
age: gentest.types.int.positive
});
gentest.sample(person);
// ->
// [ { name: '', age: 1 },
//   { name: '', age: 1 },
//   { name: 'y', age: 1 },
//   { name: '\$', age: 2 },
//   { name: 'v', age: 3 },
//   { name: '~', age: 2 },
//   { name: 'vA', age: 2 },
//   { name: 'u', age: 4 },
//   { name: 'QWb', age: 2 },
//   { name: '5,r', age: 3 } ]
```

#### fmap(fun, type)

Maps a function over the generated values of the given type.

```var powersOfTwo = gentest.types.fmap(function(n) {
return Math.pow(2, n);
}, gentest.types.int.nonNegative);

gentest.sample(powersOfTwo);
// -> [ 1, 1, 2, 2, 8, 4, 16, 32, 8, 2 ]
```

#### bind(type, fun)

A cousin of

`fmap`
where each generated value of
`type`
is mapped to a second generator, which is then sampled.

This allows you to combine generators in ways you couldn't with just

`fmap`
. For example, say you're testing a function similar to
`Array.prototype.indexOf`
, and you want arrays together with an element from the array:
```var t = gentest.types;
function isNonempty(xs) { return xs.length > 0; }

// Helper: Generate non-empty arrays of ints.
var intArray = t.suchThat(
isNonempty,
t.arrayOf(t.int)
);
var arrayAndElement = t.bind(
intArray,
// This function takes an array value, generated by the inner
// generator (intArray), and returns a generator: in this case,
// of elements selected from the array, paired with the array
// itself.
function(ints) {
return t.tuple([t.elements(ints), t.constantly(ints)]);
}
);
gentest.sample(arrayAndElement);
// ->
// [ [ -1, [ -1 ] ],
//   [ -2, [ -2, 2 ] ],
//   [ -2, [ -3, -2, 2 ] ],
//   [  1, [ -3, 1 ] ],
//   [ -4, [ -4, -1, 2, -4 ] ],
//   [  2, [ 2 ] ],
//   [  4, [ 4, -5 ] ],
//   [ -2, [ 1, -2, -4, -1, -4, -2 ] ],
//   [  3, [ -4, 6, -5, 6, 3 ] ],
//   [ -6, [ -6, 1, 6, -3, -6 ] ] ]
```

#### suchThat(pred, type, [maxTries])

Produces values of

`type`
that pass the predicate
`pred`
. This should be a predicate that will pass most of the time; you can't use this to select for relatively rare values like prime numbers, perfect squares, strings with balanced parentheses, etc.

A common use case is non-empty arrays:

```function isNonempty(xs) { return xs.length > 0; }

var nonemptyArray = t.suchThat(
isNonempty,
t.arrayOf(t.int)
);
```

If you can, it's better to generate the values you want directly instead of filtering for them. For example, this is a not-so-great way to generate multiples of 3:

```var threesBad = t.suchThat(
function(n) { return n%3 === 0; },
t.int.nonNegative);
```

This is a better way, more reliable and efficient:

```var threesGood = t.fmap(
function(n) { return n*3; },
t.int.nonNegative);
```

## Writing your own generators

A design goal of Gentest is that you as a user should never have to write your own generators from scratch. Instead, everything you need to test should be expressible in terms of the primitives above and the higher-order generators like

`fmap`
and
`bind`
.

By doing it this way you get shrinking and repeatability of test cases automatically for your new types.

How does this work in practice? Let's say you have a rectangle class which contains x and y coordinates, a width, a height, and a method to test if it's colliding with another rectangle:

```var Rect = function(x, y, w, h) {
this.x = x;
this.y = y;
this.w = w;
this.h = h;
}
Rect.prototype.isColliding = function(other) { /* ... */ };
```

To make a

`Rect`
, you essentially just need to create x, y, width and height values and pass them to the constructor. You can generate the values using
`tuple`
:
```t.tuple([t.int,          t.int,          // x, y
t.int.positive, t.int.positive  // width, height. We don't want
// these to be 0 or negative!
]);
```

Then map a function over each generated value:

```var genRect =
t.fmap(
function(tuple) {
return new Rect(tuple[0], tuple[1], tuple[2], tuple[3]);
},
t.tuple([t.int,          t.int,
t.int.positive, t.int.positive]));
```

And now use

`genRect`
in your properties just like a built-in type:
```forAll([genRect], 'rectangles collide with themselves', function(rect) {
return rect.isColliding(rect);
});
```

Avoid calling

`Math.random`
in your functions, since if you do so, test runs won't be repeatable. All randomness should come from the built-in generators.

If the generator you want seems impossible to write, check the issues because something may be missing. And feel free to ask for help. But in general, with

`fmap`
and
`bind`
you have a lot of power to build more sophisticated generators.

## Credits

gentest is heavily influenced by QuickCheck and test.check.

We use cookies. If you continue to browse the site, you agree to the use of cookies. For more information on our use of cookies please see our Privacy Policy.