@tonkite/jest-tolk
Version:
<p align="center"> <picture> <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/tonkite/tonkite/main/assets/logo-dark.svg"> <img alt="tonkite logo" src="https://raw.githubusercontent.com/tonkite/tonkite/main/a
229 lines (168 loc) • 7.49 kB
Markdown
<p align="center">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/tonkite/tonkite/main/assets/logo-dark.svg">
<img alt="tonkite logo" src="https://raw.githubusercontent.com/tonkite/tonkite/main/assets/logo-light.svg" width="384" height="auto">
</picture>
</p>
<p align="center">
<a href="https://ton.org"><img alt="Based on TON" src="https://img.shields.io/badge/Based%20on-TON-blue"></a>
<a href="https://github.com/tonkite/tonkite"><img alt="GitHub Repo stars" src="https://img.shields.io/github/stars/tonkite/tonkite"></a>
<a href="https://t.me/tonkite"><img alt="Telegram Channel" src="https://img.shields.io/badge/Telegram%20-@tonkite-24A1DE"></a>
<a href="https://opensource.org/licenses/Apache-2.0"><img alt="License" src="https://img.shields.io/badge/License-Apache_2.0-green.svg"></a>
</p>
---
# Jest Runner for Tolk
A Jest runner that enables you to write and execute unit tests for Tolk code using familiar testing patterns.
## Example
Consider a simple Tolk function in `sum.tolk`:
```kotlin
fun calculateSum(a: int, b: int) {
return a + b;
}
```
You can write unit tests for this function using Tolk:
```kotlin
import "../node_modules/@tonkite/jest-tolk/testing.tolk"
import "sum.tolk"
// @scope sum()
get test_returns_sum_of_numbers() {
val a: int = 4;
val b: int = 7;
assert(calculateSum(a, b) == a + b) throw 100;
}
fun cast<T, X>(value: X): T asm "NOP";
// @scope sum()
get test_fails_if_value_is_not_int() {
test.expectExitCode(7); // type check error
val a: int = cast<int>(null);
val b: int = 7;
calculateSum(a, b);
}
```
Test result:
<img alt="Result" src="./images/test-result.png" width="343" height="auto">
## Installation
1. Install `@tonkite/jest-tolk`:
```shell
pnpm add -D @tonkite/jest-tolk
```
2. Add runner configuration to `jest.config.ts`:
```typescript
import type { Config } from 'jest';
const config: Config = {
projects: [
{
displayName: 'test',
preset: 'ts-jest',
testEnvironment: 'node',
testPathIgnorePatterns: ['/node_modules/', '/dist/'],
},
{
displayName: 'tolk',
moduleFileExtensions: ['tolk'],
testMatch: ['**/*[._]test.tolk'],
runner: '@tonkite/jest-tolk',
},
],
};
export default config;
```
## Fuzzing
The Runner comes with native fuzz-testing support. For every fuzz test (starting from `testFuzz_`) it:
1. Generates random values for each argument.
2. Executes the test repeatedly (default 100 runs).
3. Stops on the first failure and prints the failing inputs.
Adjust the iteration count with the `@runs` annotation.
### Fuzz-able types
- Signed and unsigned integers of any width — `int`, `int8`, `uint231`, etc.
(`bool` is treated as a one-bit unsigned int).
- `address` (including `addr_std`, `addr_extern` and `addr_none`)
### Example:
```kotlin
fun div(x: int, y: int): int
asm "DIV";
/** @runs 1000 */
get testFuzz_div(x: int8, y: uint16) {
if (y == 0) {
test.expectExitCode(4); // division by zero
}
Assert.equal(x / y, div(x, y));
}
```
## Annotations
The runner allows you to configure the behavior of tests using special annotations in comments.
### Example:
```kotlin
/**
* @scope examples
*/
get test_fail_with_exit_code_500() {
test.expectExitCode(500);
throw 500;
}
```
### Supported Annotations:
| Annotation | Example | Description |
|:------------------------|:-------------------------|:---------------------------------------------------------------------------------------|
| `@scope [scope]` | `// @scope Pool::onSwap` | Specifies the scope of a test (useful for test grouping). |
| `@skip` | `// @skip` | Marks a test to be skipped. |
| `@todo` | `// @todo` | Marks a test to be done later. |
| `@gasLimit [gas limit]` | `// @gasLimit 50000` | Sets a gas limit for a test. Default: `10000`. |
| `@runs [runs]` | `// @runs 1000` | Sets a number of iterations for fuzzing. Default: `100`. |
| `@no-main` | `// @no-main` | Disables adding an entrypoint `fun main() {}` to avoid collision with an existing one. |
## Test Helpers & Assertions
### Test Helpers
* **`test.expectExitCode(exitCode: int)`**
Signals that the *current test* must terminate with `exitCode`. The runner fails the test if any other exit code (or none) is produced.
* **`test.assume(condition: bool)`**
Skips the remainder of the test when `condition` is `false`. Useful in fuzz/property tests to ignore meaningless input.
* **`test.getC7(): tuple`**
Returns the current value of control register *C7*.
* **`test.setC7(c7: tuple)`**
Replaces the entire *C7* register with `c7`. Use with care.
* **`test.setConfigParam<T>(value: T, index: int)`**
Internal helper that overwrites **one slot** in `C7` (`value` is generic).
* **`test.setTime(now: int)`**
Overrides `blockchain.now()` for deterministic testing (config-param #3).
* **`test.setBlockLogicalTime(blockLT: int)`**
Overrides the **block** logical-time stamp (config-param #4).
* **`test.setLogicalTime(lt: int)`**
Overrides the **transaction** logical-time stamp (config-param #5).
* **`test.setOriginalBalance(balance: coins, extraCurrencies: dict? = null)`**
Fakes the contract’s starting balance (config-param #7).
### Assertions
* **`Assert.equal(actual: int, expected: int, msg: slice = "")`**
* **`Assert.notEqual(actual: int, expected: int, msg: slice = "")`**
* **`Assert.equalAddress(actual: address, expected: address, msg: slice = "")`**
* **`Assert.isNull<T>(value: T, msg: slice = "")`**
* **`Assert.notNull<T>(value: T, msg: slice = "")`**
* **`Assert.isTrue(flag: bool, msg: slice = "")`**
* **`Assert.isFalse(flag: bool, msg: slice = "")`**
* **`Assert.greaterThan(a: int, b: int, msg: slice = "")`**
* **`Assert.greaterThanOrEqual(a: int, b: int, msg: slice = "")`**
* **`Assert.lessThan(a: int, b: int, msg: slice = "")`**
* **`Assert.lessThanOrEqual(a: int, b: int, msg: slice = "")`**
* **`Assert.size(t: tuple, expected: int, msg: slice = "")`**
* **`Assert.isInternalAddress(addr: address, msg: slice = "")`**
* **`Assert.isExternalAddress(addr: address, msg: slice = "")`**
* **`Assert.isNoneAddress(addr: address, msg: slice = "")`**
* **`Assert.consumesLessThan(fn: () -> void, gasLimit: int, msg: slice = "")`**
* **`Assert.fail(message: slice, code: slice = "")`**
### Example Usage
```func
get sample_test() {
;; Freeze time and set deterministic logical-times
test.setTime(1_726_689_600); ;; 2025-06-23 UTC
test.setBlockLogicalTime(1_234_567);
test.setLogicalTime(1_234_568);
;; Basic assertions
Assert.isTrue(2 + 2 == 4);
Assert.equal(0xdead, 0xdead);
;; Expect exit-code 13 from the next call
test.expectExitCode(13);
myContract.someDangerousCall();
}
```
Happy testing 🚀
## License
<a href="https://opensource.org/licenses/Apache-2.0"><img src="https://img.shields.io/badge/License-Apache_2.0-green.svg" alt="License"></a>