7.3 Doko JS testing framework
In this module, we’ll walk through writing and running tests using the DokoJS framework. DokoJS gives a powerful yet intuitive toolkit for writing and running tests for Leo programs. It will generate automatically typescript code for interacting with and testing your program. Here, we'll look looking closely at how contracts are interacted with in TypeScript, how test files are structured, and how to run your tests in different modes. If you’ve used Jest or Mocha before, DokoJS will feel right at home. But since we’re dealing with zero-knowledge proofs, private programs, and encrypted records, we’ll need to handle a few extra things under the hood.
Intallation
First head to: https://github.com/venture23-aleo/doko-js
And make sure to follow all the instructions to install latest version of Doko JS.
Setting Up Doko JS
Initialize a new project by giving the name of the project.
dokojs init <PROJECT_NAME>
First, make sure your project is initialized and compiled. You should already have this structure (or something like it):
my-project/
├── programs/
│ └── token.leo
├── artifacts/
│ ├── js/
│ └── leo/
├── test/
│ └── token.test.ts
├── aleo-config.js
└── ...
If you don’t have a test/
directory yet, create one now. This is where all your .test.ts
files will live.
You should then update the aleo-config.js
file with custom key and network environement variable values.
Adding or Modifying a Program
To add a new Leo program, simply create a new file inside the programs/
directory. If you're updating an existing one, you can either modify the file directly or use the CLI command:
dokojs add [PROGRAM_NAME]
This streamlines program setup and ensures consistency across your project.
Compilation
Once your programs are in place, compile the project by running:
dokojs compile
This command generates an artifacts/
folder containing two key directories:
leo – This folder holds the compiled Leo packages. For every
.leo
file insideprograms/
, a corresponding package is created here. DokoJS copies your Leo source code intosrc/main.leo
, compiles it, and if successful, produces.aleo
files and build artifacts.js – This folder contains everything needed to work with your Leo programs in TypeScript. It includes:
Type definitions (
types/
)Type conversion helpers (
js2leo
for converting JS to Leo,leo2js
for the reverse)Program interfaces (
.ts
files with transitions and mappings)
Each program gets its own fully-typed interface, making it easy to interact with your contracts in tests, scripts, or production code.
Now let’s take a look at a test file.
Writing Your First DokoJS Test
Here's an example test suite for a simple token
contract:
import { ExecutionMode } from '@doko-js/core';
import { TokenContract } from '../artifacts/js/token';
import { decrypttoken } from '../artifacts/js/leo2js/token';
import { PrivateKey } from '@provablehq/sdk';
const TIMEOUT = 200_000;
const mode = ExecutionMode.SnarkExecute;
const contract = new TokenContract({ mode });
const [admin] = contract.getAccounts();
const recipient = process.env.ALEO_DEVNET_PRIVATE_KEY3;
describe('Token Contract', () => {
test('deploy', async () => {
const tx = await contract.deploy();
await tx.wait();
}, TIMEOUT);
test('mint public tokens', async () => {
const amount = BigInt(5000);
const tx = await contract.mint_public(admin, amount);
await tx.wait();
const balance = await contract.account(admin);
expect(balance).toBe(amount);
}, TIMEOUT);
test('mint private tokens', async () => {
const amount = BigInt(10000);
const tx = await contract.mint_private(admin, amount);
const [record] = await tx.wait();
const decrypted = decrypttoken(record, process.env.ALEO_PRIVATE_KEY_TESTNET3);
expect(decrypted.amount).toBe(amount);
}, TIMEOUT);
});
Let’s break this down.
Anatomy of a Test File
Importing the contract:
TokenContract
is auto-generated during compilation. It gives you typed access to all contract methods.Execution Mode: You can choose between
SnarkExecute
andLeoRun
. Use the former to broadcast transactions to the blockchain; use the latter for local dry-runs.Getting Accounts:
contract.getAccounts()
returns addresses derived from your private keys. This maps to what's in youraleo-config.js
.Deploying and Interacting: You deploy using
contract.deploy()
, then call methods likemint_public
andmint_private
, and assert outcomes withexpect()
.
Remember: private values (like token amounts) are encrypted. To assert them, you’ll need to decrypt the record using helper functions like decrypttoken
. automatically generated
Running Your Tests
To run all tests in your project, simply run:
npm run test
To run a specific test file, pass the filename (or just part of it):
npm run test -- token
This will run any test files matching token.*.ts
.
Or run it directly with Jest for more control:
npm test -- --runInBand test/token.test.ts
The --runInBand
flag ensures your tests run sequentially, which helps when dealing with stateful blockchain tests.
Under the Hood: Where the Magic Happens
When you compile your project (dokojs compile
), DokoJS generates JS bindings for each Leo program. These files live inside artifacts/js/
and include:
artifacts/
└── js/
├── token.ts <-- Contract methods
├── js2leo/token.ts <-- Type conversion: JS -> Leo
├── leo2js/token.ts <-- Type conversion: Leo -> JS
└── types/token.ts <-- TypeScript definitions
So when you write:
import { TokenContract } from '../artifacts/js/token';
You're importing a wrapper around your Leo contract that handles all the encoding, decoding, and blockchain interactions for you. Pretty handy.
Configuration Tips
Everything is wired through aleo-config.js
. This file defines:
Private keys
Network endpoints
Execution modes
Priority fees
Here’s a quick peek at a minimal config:
export default {
accounts: [process.env.ALEO_PRIVATE_KEY],
mode: 'execute',
networks: {
testnet: {
endpoint: 'http://localhost:3030',
accounts: [process.env.ALEO_DEVNET_PRIVATE_KEY1],
priorityFee: 0.01
}
},
defaultNetwork: 'testnet'
};
Want to switch to local mode? Just change the mode
to 'evaluate'
.
Decrypting Private Records
When using mint_private
or transfer_private
, the resulting records are encrypted on-chain. To verify values, use decrypttoken()
(or your equivalent based on the contract name).
import { decrypttoken } from '../artifacts/js/leo2js/token';
const decrypted = decrypttoken(record, PRIVATE_KEY);
expect(decrypted.amount).toBe(expectedAmount);
The best way to understand how Doko JS works and how it can be useful for you is to try it directly with your own programs : look into the code it automatically generated on compilation, and writing a few tests on your own.
Last updated