Your own property based testing framework - Part 1: Generators
Tags: algorithms, softwaredevelopment, education, javascript
Published: 2020-07-03 07:48:54

---
title: Your own property based testing framework - Part 1: Generators
published: true
description: Build your own property based testing framework
tags: algorithms, softwaredevelopment, education, javascript
cover_image: https://dev-to-uploads.s3.amazonaws.com/i/0y7bn71d664kw0yqydjg.jpg

---

While understanding the internals of each tool is not required to use them properly, sometimes I personally feel the need to dig a bit into the tools I am using regularly.

If you never heard about property based testing, you might be interested by reading [this quick introduction first](https://medium.com/criteo-labs/introduction-to-property-based-testing-f5236229d237).

---

_Part 1 over 4…_

1. **Generators**
2. [Runners](https://dev.to/dubzzz/your-own-property-based-testing-framework-part-2-runners-12fl)
3. [Shrinkers](https://dev.to/dubzzz/your-own-property-based-testing-framework-part-3-shrinkers-5a9j)
4. [Runners with shrinker](https://dev.to/dubzzz/your-own-property-based-testing-framework-part-4-runners-with-shrink-53f7)

---

_Concerning property based testing everything started at the end of 2017…_

At that time I was trying to apply property based testing on User Interfaces as explained by [this article from the author of RapidCheck](https://engineering.atspotify.com/2015/06/25/rapid-check/) or [my article](https://medium.com/criteo-labs/detecting-the-unexpected-in-web-ui-fuzzing-1f3822c8a3a5). So I took the leading JavaScript property based testing library and tried to [add model based testing support](https://www.npmjs.com/package/jsverify-commands) to it… but failed.

So after weeks of trials and errors, I finally changed my mind. I wanted to understand how things worked under the hood to get why it did not work. The result of this analysis is [fast-check](https://github.com/dubzzz/fast-check/) - a property based testing framework written in TypeScript for both JavaScript and TypeScript users that tries to solve the issue I faced at that time to make model based testing real.

![Logo of fast-check](https://dev-to-uploads.s3.amazonaws.com/i/fmytv4gw8ja5kwxvrxbo.png)

Throughout this serie of articles we will see how to build our own property based testing library from scratch. Our wonderful library will be called `miniFc`, it will expose all the features required for property based testing from generation of random values to shrinking capabilities.

---

_But let's start from the beginning…_

In property based testing everything starts with generators. Generators take a random number generator and build anything out of it.

You can see generators as follow:

```ts
type Generator<T> = {
    generate(mrng: Random): T;
}
```

In the signature above `mrng` is a mutable random generator. In our case, it is a simple wrapper around [pure-rand](https://github.com/dubzzz/pure-rand/) that provides a usable random instance. The class `Random` can be implemented as follow:

```js
class Random {
    constructor(rng) {
        this.rng = rng;
    }
    next(min, max) {
        const g = prand.uniformIntDistribution(min, max, this.rng);
        this.rng = g[1];
        return g[0];
    }
}

// Can be used as follow:
// > const prand = require('pure-rand');
// > const seed = 0;
// > const mrng = new Random(prand.xoroshiro128plus(seed));
// > mrng.next(0, 50); // generate a random value between 0 (incl.) and 50 (incl.)
// >                   // using a uniform distribution, all values have the same probability
```

Let's build our first generator: the one responsible to build random integers.

```js
// const miniFc = {}

miniFc.integer = (min, max) => {
    return {
        generate(mrng) {
            return mrng.next(min, max);
        }
    };
}
// It can be used as follow:
// > miniFc.integer(0, 50).generate(mrng)
```

Our random integer `Generator` is simply returning the value forged by the passed random number generator.

But we want to go further as generators should be able to generate not only integers, they are supposed to generate barely any kind of values. In [fast-check](https://github.com/dubzzz/fast-check/), you can generate numbers, strings, arrays but also objects, functions, recursive structures and also infinite streams of any kind of stuff. In order to produce so many different kind of values without copying the same code again and again, they rely on taking root structures and deriving them into more complex ones as described below:

![From integers to anything](https://dev-to-uploads.s3.amazonaws.com/i/rm57ik9zxlodr860769m.png)

Let's first consider `boolean` and `character`.
In the diagram above they are constructed out of `integer` generator.
In order to do so, we need to introduce an extra function: `map`.
It will take an existing generator and a mapping function and create another generator out of it:

```ts
declare function map<T, U>(g: Generator<T>, mapper: (v: T) => U): Generator<U>;
```

It can be implemented as follow:

```js
function map(g, mapper) {
    return {
        generate(mrng) {
            const value = g.generate(mrng);
            return mapper(value);
        }
    };
}
```

Now that we have `map` we can implement some of our missing generators:

```js
miniFc.boolean = () => map(
    miniFc.integer(0, 1),
    Boolean
)

miniFc.character = () => map(
    miniFc.integer(0, 25),
    n => String.fromCharCode(97 + n)
)
```

In order to build others, we first need to implement a generator for tuples and a generator for arrays:

```js
miniFc.tuple = (...itemGenerators) => {
    return {
        generate(mrng) {
            return itemGenerators.map(g => g.generate(mrng));
        }
    };
}
// It can be used as follow:
// > miniFc.tuple(miniFc.integer(0, 50), miniFc.boolean()).generate(mrng)

miniFc.array = (itemGenerator) => {
    return {
        generate(mrng) {
            const size = mrng.next(0, 10);
            const content = [];
            for (let index = 0 ; index !== size ; ++index) {
                content.push(itemGenerator.generate(mrng));
            }
            return content;
        }
    };
}
// It can be used as follow:
// > miniFc.array(miniFc.character()).generate(mrng)
```

Now we can build our last generators:

```js
miniFc.string = () => map(
    miniFc.array(miniFc.character()),
    characters => characters.join('')
)

miniFc.dictionary = (valueGenerator) => map(
    miniFc.array(
        miniFc.tuple(
            miniFc.string(),
            valueGenerator
        )
    ),
    Object.fromEntries
)
```


Given all the work above, you should be able to generate values ranging from simple booleans to complex dictionaries. But our framework is far from done.

{% runkit
require("core-js");
const prand = require('pure-rand');

class Random {
    constructor(rng) {
        this.rng = rng;
    }
    next(min, max) {
        const g = prand.uniformIntDistribution(min, max, this.rng);
        this.rng = g[1];
        return g[0];
    }
}

function map(g, mapper) {
    return {
        generate(mrng) {
            const value = g.generate(mrng);
            return mapper(value);
        }
    };
}

const miniFc = {};

miniFc.integer = function(min, max) {
    return {
        generate(mrng) {
            return mrng.next(min, max);
        }
    };
}

miniFc.boolean = function() {
    return map(
        miniFc.integer(0, 1),
        Boolean
    );
}

miniFc.character = function() {
    return map(
        miniFc.integer(0, 25),
        function(n) { return String.fromCharCode(97 + n); }
    );
}

miniFc.tuple = function(...itemGenerators) {
    return {
        generate(mrng) {
            return itemGenerators.map(function(g) { return g.generate(mrng); });
        }
    };
}

miniFc.array = function(itemGenerator) {
    return {
        generate(mrng) {
            const size = mrng.next(0, 10);
            const content = [];
            for (let index = 0 ; index !== size ; ++index) {
                content.push(itemGenerator.generate(mrng));
            }
            return content;
        }
    };
}

miniFc.string = function() {
    return map(
        miniFc.array(miniFc.character()),
        function(characters) { return characters.join(''); }
    );
}

miniFc.dictionary = function(valueGenerator) {
    return map(
            miniFc.array(
            miniFc.tuple(
                miniFc.string(),
                valueGenerator
            )
        ),
        Object.fromEntries
    );
}
%}
const seed = 0;
const mrng = new Random(prand.xoroshiro128plus(seed));

const generatorA = miniFc.array(miniFc.character());
console.log(generatorA.generate(mrng));
console.log(generatorA.generate(mrng));

const generatorB = miniFc.dictionary(miniFc.string());
console.log(generatorB.generate(mrng));
console.log(generatorB.generate(mrng));
{% endrunkit %}
Full snippet at https://runkit.com/dubzzz/part-1-generators

---

Next part: https://dev.to/dubzzz/your-own-property-based-testing-framework-part-2-runners-12fl