Clarinet JS SDK
Practical guide to testing smart contracts with the Clarinet JS SDK
The Clarinet JS SDK provides a powerful testing framework for Clarity smart contracts. It integrates seamlessly with Vitest to enable comprehensive testing of your contract logic in a simulated environment.
Initial setup
Start by creating a new Node.js project or using an existing one:
$npm install $ init -y
Install the Clarinet JS SDK and required dependencies:
$npm install @hirosystems/clarinet-sdk vitest @stacks/transactions
Project structure
Create the following structure for your smart contract tests:
Your first test
Create a simple test file to verify your contract works:
import { describe, expect, it } from "vitest";import { Cl } from "@stacks/transactions";const accounts = simnet.getAccounts();const wallet = accounts.get("wallet_1")!;describe("counter contract", () => {it("increments the count", () => {const countUpCall = simnet.callPublicFn("counter", "count-up", [], wallet);expect(countUpCall.result).toBeOk(Cl.bool(true));});});
The simnet
object is automatically available in your tests and provides a simulated Stacks blockchain environment.
Configuration options
Clarinet configuration
Create a Clarinet.toml
file to define your contracts:
[project]name = "my-project"[contracts.counter]path = "contracts/counter.clar"[repl]costs_version = 2parser_version = 2
TypeScript setup
Configure TypeScript for the SDK:
{"compilerOptions": {"target": "ES2020","module": "ESNext","moduleResolution": "node","strict": true,"esModuleInterop": true,"skipLibCheck": true,"types": ["vitest/globals"]},"include": ["tests/**/*.ts"],"exclude": ["node_modules"]}
Vitest configuration
Set up Vitest to work with the SDK:
import { defineConfig } from "vitest/config";import { vitestSetupFilePath } from "@hirosystems/clarinet-sdk/vitest";export default defineConfig({test: {environment: "node",globals: true,setupFiles: [vitestSetupFilePath],},});
The vitestSetupFilePath
must be included in your setupFiles
array for the SDK to work properly.
Package scripts
Add test scripts to your package.json
:
"scripts": {"test": "vitest run","test:watch": "vitest","test:coverage": "vitest run --coverage"}
Common patterns
Testing read-only functions
When testing functions that don't modify state, use callReadOnlyFn
:
const getCountCall = simnet.callReadOnlyFn("counter","get-count",[Cl.principal(wallet)],wallet);expect(getCountCall.result).toBeUint(1);
Testing public functions with parameters
Pass parameters using the appropriate Clarity type helpers:
const depositCall = simnet.callPublicFn("defi","deposit",[Cl.uint(1000)], // Convert JS number to Clarity uintwallet);expect(depositCall.result).toBeOk(Cl.bool(true));
Accessing contract state
Check data variables and maps directly:
// Get a data variableconst totalDeposits = simnet.getDataVar("defi", "total-deposits");expect(totalDeposits).toBeUint(1000);// Get a map entryconst balance = simnet.getMapEntry("defi", "balances", Cl.principal(wallet));expect(balance).toBeUint(1000);
Examples
Testing contract deployment
Ensure your contract is properly deployed before testing its functions:
it("ensures the contract is deployed", () => {const contractSource = simnet.getContractSource("counter");expect(contractSource).toBeDefined();});
Testing error conditions
Verify your contract handles errors correctly:
it("fails when borrowing too much", () => {const borrowCall = simnet.callPublicFn("defi","borrow",[Cl.uint(10000)], // Amount exceeds allowedwallet);expect(borrowCall.result).toBeErr(Cl.uint(300)); // err-overborrow});
Testing with multiple accounts
Simulate interactions between different users:
const wallet1 = accounts.get("wallet_1")!;const wallet2 = accounts.get("wallet_2")!;// Wallet 1 depositssimnet.callPublicFn("defi", "deposit", [Cl.uint(1000)], wallet1);// Wallet 2 tries to withdraw wallet 1's funds (should fail)const withdrawCall = simnet.callPublicFn("defi","withdraw",[Cl.uint(1000)],wallet2);expect(withdrawCall.result).toBeErr(Cl.uint(401)); // err-unauthorized
Running tests
Execute your test suite:
$npm test
Generate coverage reports:
$npm run test:coverage
This creates detailed coverage and cost analysis for your contract functions.
Advanced usage
Using SDK in existing projects
If you have an existing JavaScript/TypeScript project with custom structure, you can still use the SDK:
Specify the Clarinet.toml location in your Vitest config:
export default defineConfig({test: {environment: "node",globals: true,setupFiles: [vitestSetupFilePath],env: {CLARINET_MANIFEST_PATH: "./blockchain/Clarinet.toml"}},});