Writing unit tests
Unit testing is the process of testing individual components or functions of smart contracts to ensure they work as expected. The Clarinet JS SDK provides a testing framework that allows you to write these tests using the Vitest testing framework, helping you catch bugs and errors early in the development process.
What you'll learn
Set up your project
Start by creating a new project with the Clarinet CLI. This command creates a project structure with the necessary files and folders, including the Clarinet JS SDK already set up for testing:
$clarinet new stx-defi$cd stx-defi
After changing into your project directory, install the package dependencies:
$npm install
Create the contract
Generate a new contract file using the Clarinet CLI:
$clarinet contract new defi
Replace the contents of contracts/defi.clar
with this DeFi contract:
;; Holds the total amount of deposits in the contract(define-data-var total-deposits uint u0);; Maps a user's principal address to their deposited amount(define-map deposits { owner: principal } { amount: uint });; Public function for users to deposit STX into the contract(define-public (deposit (amount uint))(let(;; Fetch the current balance or default to 0 if none exists(current-balance (default-to u0 (get amount (map-get? deposits { owner: tx-sender })))));; Transfer the STX from sender to contract(try! (stx-transfer? amount tx-sender (as-contract tx-sender)));; Update the user's deposit amount in the map(map-set deposits { owner: tx-sender } { amount: (+ current-balance amount) });; Update the total deposits variable(var-set total-deposits (+ (var-get total-deposits) amount));; Return success(ok true)));; Read-only function to get the balance by tx-sender(define-read-only (get-balance-by-sender)(ok (map-get? deposits { owner: tx-sender })))
Run clarinet check
to ensure your contract is valid:
$clarinet check[32m✔ 1 contract checked[0m
Write your unit test
The key tests we want to cover are that the deposit is successful and that the user's balance, as well as the contract's total deposits, are updated correctly.
Replace the contents of tests/defi.test.ts
:
import { describe, it, expect } from 'vitest';import { Cl } from '@stacks/transactions';const accounts = simnet.getAccounts();const wallet1 = accounts.get('wallet_1')!;describe('stx-defi', () => {it('allows users to deposit STX', () => {// Define the amount to depositconst amount = 1000;// Call the deposit functionconst deposit = simnet.callPublicFn('defi', 'deposit', [Cl.uint(amount)], wallet1);// Assert the deposit was successfulexpect(deposit.result).toBeOk(Cl.bool(true));// Verify the contract's total depositsconst totalDeposits = simnet.getDataVar('defi', 'total-deposits');expect(totalDeposits).toBeUint(amount);// Check the user's balanceconst balance = simnet.callReadOnlyFn('defi', 'get-balance-by-sender', [], wallet1);expect(balance.result).toBeOk(Cl.some(Cl.tuple({amount: Cl.uint(amount),})));});});
Let's break down the key parts of this test:
simnet.callPublicFn
- Calls a public function in your contractexpect().toBeOk()
- Custom matcher that checks for Clarityok
responsesimnet.getDataVar
- Retrieves the value of a data variableCl
helpers - Create Clarity values in JavaScript
Try it out
Run your test to see it in action:
$npm run testPASS tests/defi.test.tsstx-defi✓ allows users to deposit STX (5 ms)Test Files 1 passed (1)Tests 1 passed (1)
Common issues
Issue | Solution |
---|---|
toBeOk is not a function | Ensure you're using the custom matchers from the SDK |
Contract not found | Run clarinet check to validate your contract |
Type errors | Use the Cl helpers to create proper Clarity values |