Overview
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 with Vitest to let you run comprehensive tests against a simulated blockchain environment.
Initial setup
Create a new Node.js project (or reuse an existing one):
npm init -yInstall the Clarinet JS SDK and its dependencies:
npm install @hirosystems/clarinet-sdk vitest @stacks/transactionsProject structure
Organize your project so contracts and tests live together:
- my-project/
- contracts/
- counter.clar
- tests/
- counter.test.ts
- Clarinet.toml
- package.json
- tsconfig.json
- vitest.config.jsSimple test
Create tests/counter.test.ts to verify the contract:
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 and exposes a simulated Stacks blockchain.
Configuration options
Clarinet configuration
Define your contracts in Clarinet.toml:
[project]
name = "my-project"
[contracts.counter]
path = "contracts/counter.clar"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 so the SDK can bootstrap the testing environment:
import { defineConfig } from "vitest/config";
import { vitestSetupFilePath } from "@hirosystems/clarinet-sdk/vitest";
export default defineConfig({
test: {
environment: "node",
globals: true,
setupFiles: [vitestSetupFilePath],
},
});Package scripts
Add convenient test scripts to package.json:
"scripts": {
"test": "vitest run",
"test:watch": "vitest",
"test:coverage": "vitest run --coverage"
}Common patterns
Testing read-only functions
Use callReadOnlyFn for functions that do not modify state:
const getCountCall = simnet.callReadOnlyFn(
"counter",
"get-count",
[Cl.principal(wallet)],
wallet
);
expect(getCountCall.result).toBeUint(1);Testing public functions with parameters
Pass parameters with the appropriate Clarity helpers:
const depositCall = simnet.callPublicFn(
"defi",
"deposit",
[Cl.uint(1000)],
wallet
);
expect(depositCall.result).toBeOk(Cl.bool(true));Accessing contract state
Inspect data variables and maps directly:
const totalDeposits = simnet.getDataVar("defi", "total-deposits");
expect(totalDeposits).toBeUint(1000);
const balance = simnet.getMapEntry("defi", "balances", Cl.principal(wallet));
expect(balance).toBeUint(1000);Examples
Testing contract deployment
Ensure the contract was deployed:
it("ensures the contract is deployed", () => {
const contractSource = simnet.getContractSource("counter");
expect(contractSource).toBeDefined();
});Testing error conditions
Verify error handling logic:
it("fails when borrowing too much", () => {
const borrowCall = simnet.callPublicFn(
"defi",
"borrow",
[Cl.uint(10000)], // Amount exceeds allowed
wallet
);
expect(borrowCall.result).toBeErr(Cl.uint(300)); // err-overborrow
});Testing with multiple accounts
Simulate cross-account interactions:
const wallet1 = accounts.get("wallet_1")!;
const wallet2 = accounts.get("wallet_2")!;
// Wallet 1 deposits
simnet.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-unauthorizedRunning tests
Execute the full suite:
npm testGenerate coverage reports:
npm run test:coverageCoverage output includes cost analysis for your contract functions.
Advanced usage
Using the SDK in existing projects
If your project has a custom structure, keep contracts and tests together and point the SDK to the manifest:
- my-app/
- blockchain/
- contracts/
- token.clar
- tests/
- token.test.ts
- Clarinet.toml
- frontend/
- App.tsx
- package.json
- vitest.config.jsUpdate vitest.config.js to reference the correct manifest path:
export default defineConfig({
test: {
environment: "node",
globals: true,
setupFiles: [vitestSetupFilePath],
env: {
CLARINET_MANIFEST_PATH: "./blockchain/Clarinet.toml"
}
},
});Last updated
Was this helpful?
