Convert Figma logo to code with AI

leebyron logotestcheck-js

Generative testing for JavaScript

1,183
39
1,183
28

Top Related Projects

Property based testing framework for JavaScript (like QuickCheck) written in TypeScript

Write powerful and concise tests. Property-based testing for JavaScript. Like QuickCheck.

44,166

Delightful JavaScript Testing.

5,771

tap-producing test harness for node and browsers

46,847

Fast, easy and reliable testing for anything that runs in a browser.

Quick Overview

TestCheck.js is a generative testing library for JavaScript, inspired by QuickCheck for Haskell. It allows developers to write property-based tests that automatically generate test cases, helping to uncover edge cases and unexpected behaviors in code.

Pros

  • Automatically generates test cases, reducing the need for manual test writing
  • Helps discover edge cases and unexpected behaviors that might be missed in traditional unit testing
  • Supports both Node.js and browser environments
  • Integrates well with popular testing frameworks like Jest and Mocha

Cons

  • Requires a different approach to testing, which may have a learning curve for developers used to traditional unit testing
  • Can be slower than traditional unit tests due to the generation of multiple test cases
  • May not be suitable for all types of tests, especially those requiring specific, predetermined inputs
  • Documentation could be more comprehensive for advanced use cases

Code Examples

  1. Basic property test:
import { check, gen } from 'testcheck';

const result = check(
  gen.int,
  (x) => x + 0 === x
);

console.log(result);

This example checks if adding zero to any integer always results in the same integer.

  1. Testing a custom function:
import { check, gen } from 'testcheck';

function isEven(n) {
  return n % 2 === 0;
}

const result = check(
  gen.int,
  (x) => isEven(x * 2)
);

console.log(result);

This test verifies that multiplying any integer by 2 always results in an even number.

  1. Using custom generators:
import { check, gen } from 'testcheck';

const positiveIntGen = gen.posInt;
const negativeIntGen = gen.negInt;

const result = check(
  [positiveIntGen, negativeIntGen],
  (pos, neg) => pos > neg
);

console.log(result);

This example uses custom generators to test if a positive integer is always greater than a negative integer.

Getting Started

To use TestCheck.js in your project:

  1. Install the library:

    npm install testcheck
    
  2. Import and use in your tests:

    import { check, gen } from 'testcheck';
    
    // Write your property-based tests
    const result = check(
      gen.string,
      (s) => s.length >= 0
    );
    
    console.log(result);
    
  3. Run your tests using your preferred test runner (e.g., Jest, Mocha).

Competitor Comparisons

Property based testing framework for JavaScript (like QuickCheck) written in TypeScript

Pros of fast-check

  • More active development and frequent updates
  • Broader range of built-in generators and arbitraries
  • Better TypeScript support and type inference

Cons of fast-check

  • Steeper learning curve due to more advanced features
  • Slightly more verbose API in some cases
  • Less established in the JavaScript community compared to testcheck-js

Code Comparison

testcheck-js:

check(
  property(gen.int, gen.int, (a, b) => a + b === b + a),
  {times: 1000}
);

fast-check:

fc.assert(
  fc.property(fc.integer(), fc.integer(), (a, b) => a + b === b + a),
  {numRuns: 1000}
);

Both libraries provide similar functionality for property-based testing, but fast-check offers more advanced features and customization options. testcheck-js has a simpler API, which may be preferable for basic use cases. fast-check's extensive documentation and active development make it a strong choice for complex testing scenarios, especially in TypeScript projects. However, testcheck-js might be more suitable for developers looking for a straightforward, easy-to-use library with a smaller learning curve.

Write powerful and concise tests. Property-based testing for JavaScript. Like QuickCheck.

Pros of jsverify

  • More extensive documentation and examples
  • Wider range of built-in generators and combinators
  • Better integration with popular testing frameworks like Mocha and Jasmine

Cons of jsverify

  • Slightly more complex API, which may have a steeper learning curve
  • Less focus on performance optimization compared to testcheck-js

Code Comparison

testcheck-js:

check(
  gen.array(gen.int),
  (arr) => arr.indexOf(Math.max(...arr)) === arr.lastIndexOf(Math.max(...arr))
);

jsverify:

jsc.property(
  "array contains maximum value only once",
  "array nat",
  function (arr) {
    return arr.indexOf(Math.max(...arr)) === arr.lastIndexOf(Math.max(...arr));
  }
);

Both libraries provide similar functionality for property-based testing, but jsverify offers a more expressive API with built-in support for common data types. testcheck-js, on the other hand, has a simpler API that may be easier for beginners to grasp.

While jsverify provides more features and integrations, testcheck-js focuses on performance and simplicity. The choice between the two depends on the specific needs of the project and the developer's preferences.

44,166

Delightful JavaScript Testing.

Pros of Jest

  • Comprehensive testing framework with built-in assertion library, mocking, and code coverage
  • Extensive documentation and large community support
  • Seamless integration with popular JavaScript frameworks like React

Cons of Jest

  • Heavier setup and potentially slower execution for small projects
  • Less focus on property-based testing compared to TestCheck.js
  • May require additional configuration for certain edge cases or complex scenarios

Code Comparison

TestCheck.js:

check.it('reverses arrays', [gen.array(gen.int)], function(arr) {
  expect(reverse(arr)).toEqual(arr.slice().reverse());
});

Jest:

test('reverses arrays', () => {
  const arr = [1, 2, 3, 4, 5];
  expect(reverse(arr)).toEqual([5, 4, 3, 2, 1]);
});

Key Differences

  • TestCheck.js focuses on property-based testing, generating multiple test cases automatically
  • Jest provides a more traditional unit testing approach with predefined test cases
  • TestCheck.js requires less boilerplate for generating test data, while Jest offers more flexibility in test structure

Use Cases

  • TestCheck.js: Ideal for testing pure functions and algorithms with complex input requirements
  • Jest: Well-suited for general-purpose JavaScript testing, particularly in larger projects and web applications
5,771

tap-producing test harness for node and browsers

Pros of tape

  • Simpler and more lightweight testing framework
  • Easier to get started with for basic testing needs
  • Produces TAP-compliant output, which is widely supported

Cons of tape

  • Lacks advanced features for property-based testing
  • Limited built-in assertion methods compared to TestCheck.js
  • May require additional plugins or libraries for more complex testing scenarios

Code Comparison

tape:

const test = require('tape');

test('basic arithmetic', (t) => {
  t.equal(2 + 2, 4, 'addition works');
  t.end();
});

TestCheck.js:

const { check, gen } = require('testcheck');

check(
  (a, b) => a + b === b + a,
  [gen.int, gen.int]
);

TestCheck.js focuses on property-based testing, allowing for more comprehensive and randomized test cases. It generates input data automatically, which can help uncover edge cases and unexpected behaviors. On the other hand, tape provides a straightforward approach to writing and running tests, making it easier to adopt for developers new to testing or those working on smaller projects.

While tape is excellent for basic unit testing, TestCheck.js shines in scenarios where you need to test complex properties and invariants of your code. The choice between the two depends on the specific needs of your project and the level of testing sophistication required.

46,847

Fast, easy and reliable testing for anything that runs in a browser.

Pros of Cypress

  • Comprehensive end-to-end testing framework with built-in GUI and real-time reloading
  • Extensive documentation and active community support
  • Simpler setup and configuration for web application testing

Cons of Cypress

  • Limited to testing web applications, unlike TestCheck.js which can test any JavaScript code
  • Heavier resource usage and slower test execution compared to TestCheck.js
  • Less flexibility in generating test data compared to TestCheck.js's property-based testing approach

Code Comparison

TestCheck.js example:

check(
  gen.int, gen.int,
  (a, b) => a + b === b + a
);

Cypress example:

describe('Addition', () => {
  it('should be commutative', () => {
    cy.wrap(1 + 2).should('equal', 2 + 1);
  });
});

Summary

Cypress is a powerful end-to-end testing framework specifically designed for web applications, offering a user-friendly interface and extensive features. TestCheck.js, on the other hand, is a lightweight property-based testing library that can be used for any JavaScript code. While Cypress excels in web application testing scenarios, TestCheck.js provides more flexibility in generating test data and can be applied to a broader range of testing situations.

Convert Figma logo designs to code with AI

Visual Copilot

Introducing Visual Copilot: A new AI model to turn Figma designs to high quality code using your components.

Try Visual Copilot

README

TestCheck.js Build Status

Generative property testing for JavaScript.

TestCheck.js is a library for generative testing of program properties, ala QuickCheck.

By providing a specification of the JavaScript program in the form of properties, the properties can be tested to remain true for a large number of randomly generated cases. In the case of a test failure, the smallest possible failing test case is found.

Getting started

Install testcheck using yarn

yarn add --dev testcheck

Or using npm

npm install --save-dev testcheck

Then require it into your testing environment and start testing.

const { check, gen, property } = require('testcheck');

const result = check(
  property(
    gen.int,
    x => x - x === 0
  )
)

Have a favorite test framework?

TestCheck.js is a testing utility and not a complete test-running framework. It doesn't replace test frameworks like AVA, Jasmine, or Mocha.

If you use AVA then check out ava-check, a testcheck AVA plugin.

const test = require('ava')
const { check, gen } = require('ava-check')

test('addition is commutative', check(gen.int, gen.int, (t, numA, numB) => {
  t.true(numA + numB === numB + numA)
}))

If you use Jasmine or Jest then check out jasmine-check, a testcheck Jasmine (or Jest) plugin.

require('jasmine-check').install()

describe('Maths', () => {
  check.it('addition is commutative', gen.int, gen.int, (numA, numB) => {
    expect(numA + numB).toEqual(numB + numA)
  })
})

If you use Mocha then check out mocha-testcheck, a testcheck Mocha plugin.

require('mocha-testcheck').install();
const { expect } = require('chai');

describe('Maths', () => {
  check.it('addition is commutative', gen.int, gen.int, (numA, numB) => {
    expect(numA + numB).to.equal(numB + numA)
  })
})

If you use Tape then check out tape-check, a testcheck Tape plugin.

const test = require('tape')
const { check, gen } = require('tape-check')

test('addition is commutative', check(gen.int, gen.int, (t, numA, numB) => {
  t.plan(1)
  t.equal(numA + numB, numB + numA)
}));

Type definitions

This module includes type definitions for Flow type and Typescript. Simply require or import this module and enjoy type suggestions and corrections.

Using TestCheck.js

See the complete API documentation for all available generators and utilities, or the Walkthrough Guide for a more thorough walkthrough.

Try it! Open the developer console while viewing the docs to follow along with the examples below.

Defining properties

A property is simply a function which is expected to always return true, we might also call these properties "assumptions" or "expectations".

For example, say we wanted to test the assumption that any number subtracted from itself will be 0, we could define this property as:

function (x) {
  return x - x === 0
}

Or as another example, let's determine that sorting an array is stable and idempotent, which is to say that sorting a sorted array shouldn't do anything. We could write:

function (arr) {
  var arrCopy = arr.slice()
  return deepEqual(arrCopy.sort(), arr.sort().sort())
}

That's really it! The only thing special about this property function is that it is pure, e.g. it relies only on the provided arguments to determine its return value (no other reading or writing!).

If you can start to describe your program in terms of its properties, then testcheck can test them for you.

Generating test cases

Once we've defined some properties, we generate test cases for each properties by describing the types of values for each argument.

For testing our first property, we need numbers:

gen.int

For the second, we need arrays of numbers

gen.array(gen.int)

There are a wide variety of value generators, we've only scratched the surface. We can generate random JSON with gen.JSON, pick amongst a set of values with gen.returnOneOf, nested arrays with ints gen.nested(gen.array, gen.int) and much more. You can even define your own generators with generator.then(), and gen.sized.

Checking the properties

Finally, we check our properties using our test case generator (in this case, up to 1000 different tests before concluding).

const result = check(
  property(
    // the arguments generator
    gen.int,
    // the property function to test
    x => x - x === 0
  ),
  { numTests: 1000 }
)

check runs through random cases looking for failure, and when it doesn't find any failures, it returns:

{ result: true, numTests: 1000, seed: 1406779597155 }

Smallest failing test

Let's try another property: the sum of two integers is the same or larger than either of the integers alone.

check(
  property(
    gen.int, gen.int,
    (a, b) => a + b >= a && a + b >= b
  )
)

check runs through random cases again. This time it found a failing case, so it returns:

{ result: false,
  failingSize: 2,
  numTests: 3,
  fail: [ 2, -1 ],
  shrunk:
   { totalNodesVisited: 2,
     depth: 1,
     result: false,
     smallest: [ 0, -1 ] } }

Something is wrong. Either:

  1. Our assumption is wrong (e.g. bug in our software).
  2. The test code is wrong.
  3. The generated test data is too broad.

In this case, our problem is that our generated data is too broad for our assumption. What's going on?

We can see that the fail case 2, -1 would in fact not be correct, but it might not be immediately clear why. This is where test case shrinking comes in handy. The shrunk key provides information about the shrinking process and most importantly, the smallest values that still fail: 0, -1.

We forgot about an edge case! If one of the integers is negative, then the sum will not be larger. This shrunken test case illustrated this much better than the original failing test did. Now we know that we can either improve our property or make the test data more specific:

check(property(
  gen.posInt, gen.posInt,
  (a, b) => a + b >= a && a + b >= b
));

With our correction, our property passes all tests.

Thinking in random distributions

It's important to remember that your test is only as good as the data being provided. While testcheck provides tools to generate random data, thinking about what that data looks like may help you write better tests. Also, because the data generated is random, a test may pass which simply failed to uncover a corner case.

"Testing shows the presence, not the absence of bugs"

— Dijkstra, 1969

Sampling Test Data

Visualizing the data check generates may help diagnose the quality of a test. Use sample and sampleOne to get a look at what a generator produces:

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

sample(gen.int)
// [ 0, 0, 2, -1, 3, 5, -4, 0, 3, 5 ]

sampleOne(gen.int)
// -23

The Size of Test Data

Test data generators have an implicit size property, which could be used to determine the maximum value for a generated integer or the max length of a generated array. testcheck begins by generating small test cases and gradually increases the size.

So if you wish to test very large numbers or extremely long arrays, running check the default 100 times with maxSize of 200, you may not get what you expect.

Data relationships

Let's test an assumption that should clearly be wrong: a string split by another string always returns an array of length 1.

check(property(
  gen.asciiString.notEmpty(), gen.asciiString.notEmpty(),
  (str, separator) => str.split(separator).length === 1
))

Unless you got lucky, you probably saw this check pass. This is because we're testing for a relationship between these strings. If separator is not found in str, then this test passes. The second unrelated random string is very unlikely to be found within the first random string.

We could change the test to be aware of this relationship such that the separator is always contained within the str by using then().

check(property(
  gen.asciiString.notEmpty().then(str =>
    [ str, gen.substring(str).notEmpty() ]),
  ([ str, separator ]) => str.split(separator).length === 1
))

Now separator is a random substring of str and the test fails with the smallest failing arguments: [ ' ', ' ' ].

We can test this example out ourselves, with the value ' ' generated for both str and separator, we can run ' '.split(' ').length to see that we in fact get 2, not 1.

License

Copyright 2014-Present Lee Byron

TestCheck.js is distributed under the BSD-3-Clause license.

Atop the shoulders of giants

TestCheck.js is based on Clojure's test.check which is inspired by Haskell's QuickCheck. Many gracious thanks goes to all of the brilliance and hard work enabling this project to exist.

Clojure's test.check is Copyright Rich Hickey, Reid Draper and contributors and is distributed under the Eclipse Public License.

NPM DownloadsLast 30 Days