to be valid for all
- <function> to be valid for all <chance-generator+>
- <function> to be valid for all <object>
An assertion that runs the subject function up to 300 times with different
generated input. If the subject function fails an error is thrown.
Let's tests that
lodash.unescape reverses
lodash.escape we do that the
following way:
const escape = require('lodash.escape');
const unescape = require('lodash.unescape');
const { string } = require('chance-generators');
expect(
(text) => {
expect(unescape(escape(text)), 'to equal', text);
},
'to be valid for all',
string({ max: 200 })
);
This will run 300 tests with random strings of length 0-200 and succeed.
You can specify the max number of iterations that the test should run and the
number of errors it should collect before stopping.
The algorithm searches for the smallest error output, so the more errors you
allow it to collect the better the output will be.
expect(
(text) => {
expect(unescape(escape(text)), 'to equal', text);
},
'to be valid for all',
{
generators: [string({ max: 200 })],
maxIterations: 1000,
maxErrors: 30,
}
);
I found to following code for
Run-length encoding on the
internet, let's see
if that code also fulfill our round trip test:
function rleEncode(input) {
var encoding = [];
input.match(/(.)\1*/g).forEach((substr) => {
encoding.push([substr.length, substr[0]]);
});
return encoding;
}
function rleDecode(encoded) {
var output = '';
encoded.forEach((pair) => {
output += new Array(1 + pair[0]).join(pair[1]);
});
return output;
}
expect(
(text) => {
expect(rleDecode(rleEncode(text)), 'to equal', text);
},
'to be valid for all',
string({ max: 200 })
);
Found an error after 220 iterations
counterexample:
Generated input: ''
with: string({ min: 0, max: 200 })
TypeError: Cannot read property 'forEach' of null
at rleEncode (evalmachine.<anonymous>:3:25)
at string.max (evalmachine.<anonymous>:18:20)
at /Users/andreas.lind/code/unexpected-check/lib/unexpected-check.js:276:36
at /Users/andreas.lind/code/unexpected-check/node_modules/unexpected/build/lib/makePromise.js:68:32
at tryCatcher (/Users/andreas.lind/code/unexpected-check/node_modules/unexpected-bluebird/js/main/util.js:26:23)
at Promise._resolveFromResolver (/Users/andreas.lind/code/unexpected-check/node_modules/unexpected-bluebird/js/main/promise.js:476:31)
at new Promise (/Users/andreas.lind/code/unexpected-check/node_modules/unexpected-bluebird/js/main/promise.js:69:37)
at Function.makePromise [as promise] (/Users/andreas.lind/code/unexpected-check/node_modules/unexpected/build/lib/makePromise.js:17:10)
at /Users/andreas.lind/code/unexpected-check/lib/unexpected-check.js:275:20
at loop (/Users/andreas.lind/code/unexpected-check/lib/unexpected-check.js:182:20)
at tryCatcher (/Users/andreas.lind/code/unexpected-check/node_modules/unexpected-bluebird/js/main/util.js:26:23)
at Promise._settlePromiseFromHandler (/Users/andreas.lind/code/unexpected-check/node_modules/unexpected-bluebird/js/main/promise.js:503:31)
at Promise._settlePromiseAt (/Users/andreas.lind/code/unexpected-check/node_modules/unexpected-bluebird/js/main/promise.js:577:18)
at Promise._settlePromises (/Users/andreas.lind/code/unexpected-check/node_modules/unexpected-bluebird/js/main/promise.js:693:14)
at Async._drainQueue (/Users/andreas.lind/code/unexpected-check/node_modules/unexpected-bluebird/js/main/async.js:123:16)
at Async._drainQueues (/Users/andreas.lind/code/unexpected-check/node_modules/unexpected-bluebird/js/main/async.js:133:10)
at Async.drainQueues (/Users/andreas.lind/code/unexpected-check/node_modules/unexpected-bluebird/js/main/async.js:15:14)
at /Users/andreas.lind/code/unexpected-check/node_modules/unexpected/build/lib/workQueue.js:7:7
at Array.forEach (<anonymous>)
at Object.drain (/Users/andreas.lind/code/unexpected-check/node_modules/unexpected/build/lib/workQueue.js:6:16)
at oathbreaker (/Users/andreas.lind/code/unexpected-check/node_modules/unexpected/build/lib/oathbreaker.js:44:13)
at Function.expectPrototype._executeExpect (/Users/andreas.lind/code/unexpected-check/node_modules/unexpected/build/lib/createTopLevelExpect.js:1517:10)
Something is failing for the empty string input. The problem is that the regular
expression in the encoder does not match the empty string. This would probably
also have been found in a unit test, but these edge cases are easily found using
property based testing. Imagine more complex scenarios where code only fails for
the null character, these cases might be harder to come up with while doing
normal unit testing.
You can supply as many generators as you want. My examples are using
chance-generators but you
can using any function that produces a random output when called.
Here is a test that uses more than one generator:
const { word } = require('chance-generators');
expect(
(a, b) => {
return (a + b).length === a.length + b.length;
},
'to be valid for all',
word,
word
);
Another example could be to generate actions.
Let's create a simple queue:
function Queue() {
this.buffer = [];
}
Queue.prototype.enqueue = function (value) {
this.buffer.push(value);
};
Queue.prototype.dequeue = function () {
return this.buffer.shift(1);
};
Queue.prototype.isEmpty = function () {
return this.buffer.length === 0;
};
Queue.prototype.drainTo = function (array) {
while (!this.isEmpty()) {
array.push(this.dequeue());
}
};
Now let's test that items enqueued always comes out in the right order:
const { array, pickone } = require('chance-generators');
var action = pickone([{ name: 'enqueue', value: string }, { name: 'dequeue' }]);
var actions = array(action, 200);
expect(
function (actions) {
var queue = new Queue();
var enqueued = [];
var dequeued = [];
actions.forEach(function (action) {
if (action.name === 'enqueue') {
enqueued.push(action.value);
queue.enqueue(action.value);
} else if (!queue.isEmpty()) {
dequeued.push(queue.dequeue());
}
});
queue.drainTo(dequeued);
expect(dequeued, 'to equal', enqueued);
},
'to be valid for all',
actions
);
Support for asynchronous testing by returning a promise from the subject
function:
expect.use(require('unexpected-stream'));
return expect(
function (text) {
return expect(
text,
'when piped through',
[require('zlib').Gzip(), require('zlib').Gunzip()],
'to yield output satisfying',
'when decoded as',
'utf-8',
'to equal',
text
);
},
'to be valid for all',
string
);