Hegel is a property-based testing library for TypeScript. Hegel is based on
Hypothesis, using the
Hegel protocol.
Getting started
This guide walks you through the basics of installing Hegel and writing your
first tests.
Install Hegel
Add @hegeldev/hegel to your project as a dev dependency:
npminstall--save-dev@hegeldev/hegel
Hegel requires Node 16+. Bun and Deno are not currently supported.
Write your first test
You're now ready to write your first test. We'll use
Vitest as the test runner for the purposes of this
guide. Create a new test file:
import { test } from"vitest"; import*ashegelfrom"@hegeldev/hegel"; import*asgsfrom"@hegeldev/hegel/generators";
test( "integer self equality", hegel.test((tc) => { constn = tc.draw(gs.integers()); if (n !== n) { thrownewError("integer was not equal to itself"); } }), );
Now run the test using npx vitest run. You should see that this test
passes.
Let's look at what's happening in more detail. test runs your test
many times (100, by default). The test function receives a TestCase,
which provides a draw method for drawing different
values. This test draws a random integer and checks that it should be equal
to itself.
Next, try a test that fails:
test( "integers always below 50", hegel.test((tc) => { constn = tc.draw(gs.integers()); if (n >= 50) { thrownewError(`n=${n} is too large`); } }), );
This test asserts that any integer is less than 50, which is obviously
incorrect. Hegel will find a test case that makes this assertion fail, and
then shrink it to find the smallest counterexample — in this case, n = 50.
To fix this test, you can constrain the integers you generate with the
minValue and maxValue options:
test( "bounded integers always below 50", hegel.test((tc) => { constn = tc.draw(gs.integers({ minValue:0, maxValue:49 })); if (n >= 50) { thrownewError(`n=${n} is too large`); } }), );
Run the test again. It should now pass.
Use generators
Hegel provides a rich library of generators that you can use out of the box.
There are primitive generators, such as integers, floats, and text,
and combinators that allow you to make generators out of other generators,
such as arrays and maps.
For example, you can use arrays to generate an array of integers:
test( "append increases length", hegel.test((tc) => { constxs = tc.draw(gs.arrays(gs.integers())); constinitialLength = xs.length; xs.push(tc.draw(gs.integers())); if (xs.length <= initialLength) { thrownewError("length did not increase"); } }), );
This test checks that appending an element to a random array of integers
should always increase its length.
You can also build composite values out of multiple generators. The simplest
way is to draw fields directly inside the test body:
interfacePerson { age: number; name: string; }
test( "person", hegel.test((tc) => { constperson: Person = { age:tc.draw(gs.integers({ minValue:0, maxValue:120 })), name:tc.draw(gs.text({ minSize:1, maxSize:50 })), }; // use person in your test voidperson; }), );
For composite values you want to reuse across tests, build a generator with
composite (imperative — call tc.draw() on inner generators inside a
builder function) or record (declarative — pass a schema mapping field
names to generators). Both produce a generator that supports .map(),
.filter(), and .flatMap() like any other generator.
Note that you can feed the results of one draw into subsequent calls — this
is what composite is for. For example, say that you extend the Person
interface to include a drivingLicense boolean field, where the field
depends on age:
test( "addition is commutative", hegel.test((tc) => { constx = tc.draw(gs.integers()); consty = tc.draw(gs.integers()); tc.note(`x + y = ${x+y}, y + x = ${y+x}`); if (x + y !== y + x) { thrownewError("addition is not commutative"); } }), );
Notes only appear when Hegel replays the minimal failing example.
Change the number of test cases
By default Hegel runs 100 test cases. To override this, pass a
Settings override as the second argument to test:
test( "integers many", hegel.test( (tc) => { constn = tc.draw(gs.integers()); if (n !== n) { thrownewError("integer was not equal to itself"); } }, { testCases:500 }, ), );
Learning more
Browse the @hegeldev/hegel/generators module for the full list of
available generators.
See Settings for more configuration settings to customize how
your test runs.
Hegel is a property-based testing library for TypeScript. Hegel is based on Hypothesis, using the Hegel protocol.
Getting started
This guide walks you through the basics of installing Hegel and writing your first tests.
Install Hegel
Add
@hegeldev/hegelto your project as a dev dependency:Hegel requires Node 16+. Bun and Deno are not currently supported.
Write your first test
You're now ready to write your first test. We'll use Vitest as the test runner for the purposes of this guide. Create a new test file:
Now run the test using
npx vitest run. You should see that this test passes.Let's look at what's happening in more detail. test runs your test many times (100, by default). The test function receives a TestCase, which provides a draw method for drawing different values. This test draws a random integer and checks that it should be equal to itself.
Next, try a test that fails:
This test asserts that any integer is less than 50, which is obviously incorrect. Hegel will find a test case that makes this assertion fail, and then shrink it to find the smallest counterexample — in this case,
n = 50.To fix this test, you can constrain the integers you generate with the
minValueandmaxValueoptions:Run the test again. It should now pass.
Use generators
Hegel provides a rich library of generators that you can use out of the box. There are primitive generators, such as
integers,floats, andtext, and combinators that allow you to make generators out of other generators, such asarraysandmaps.For example, you can use
arraysto generate an array of integers:This test checks that appending an element to a random array of integers should always increase its length.
You can also build composite values out of multiple generators. The simplest way is to draw fields directly inside the test body:
For composite values you want to reuse across tests, build a generator with
composite(imperative — calltc.draw()on inner generators inside a builder function) orrecord(declarative — pass a schema mapping field names to generators). Both produce a generator that supports.map(),.filter(), and.flatMap()like any other generator.Note that you can feed the results of one
drawinto subsequent calls — this is whatcompositeis for. For example, say that you extend thePersoninterface to include adrivingLicenseboolean field, where the field depends onage:Debug your failing test cases
Use the note method to attach debug information:
Notes only appear when Hegel replays the minimal failing example.
Change the number of test cases
By default Hegel runs 100 test cases. To override this, pass a Settings override as the second argument to test:
Learning more
@hegeldev/hegel/generatorsmodule for the full list of available generators.