GitHub
Examples of unit tests

If you want to follow along, make sure you've followed the instructions for getting setup.

The Counter contract

This example uses a simple counter contract. Here's the Clarity code:

;; The counter contract maintains a single global counter
;; variable. Users can change the counter by calling
;; `increment`.
 
;; The variable used to hold the global counter.
(define-data-var counter uint u1)
 
;; Map to track each sender's last increment
(define-map last-increment principal uint)
 
;; Get the current counter
(define-read-only (get-counter)
  (var-get counter)
)
 
;; Increment the counter. You cannot increment by more than 5.
;;
;; @returns the new value of the counter
;;
;; @param step; The interval to increase the counter by
(define-public (increment (step uint))
  (let (
    (new-val (+ step (var-get counter)))
  )
  ;; #[allow(unchecked_data)]
  (var-set counter new-val)
  (print { object: "counter", action: "incremented", value: new-val })
  (asserts! (<= step u5) (err u100))
  (map-set last-increment tx-sender step)
  (ok new-val))
)

Unit test setup

At the top of your unit testing file, you'll need to import a few things to get started:

import { projectFactory } from '@clarigen/core';
import { project, accounts } from './clarigen-types';
import { rov, rovOk, txOk, txErr, ro, varGet, mapGet, filterEvents } from '@clarigen/test';
import { test, expect } from 'vitest';
 
const { counter } = projectFactory(project, 'simnet');
const alice = accounts.wallet_1.address;
 
test('Counter contract tests with Clarigen', () => {
  // Your tests will go here
});

Calling read-only functions

Use the rov function, which stands for "read-only-value", to call a read-only function and only get the value returned. For example:

const initialValue = rov(counter.getCounter());
expect(initialValue).toEqual(1n);

If your read-only function returns a response type, use rovOk and rovErr to automatically check the response and return either the ok or err value.

Calling public functions

Use the txOk function to call a public function and check that it returns an ok. For example:

const incrementReceipt = txOk(counter.increment(1n), alice);
expect(incrementReceipt.value).toEqual(2n);

You can also use tx to get the full response value, whether the function is ok or err.

const incrementReceipt = tx(counter.increment(1n), alice);
if (incrementReceipt.value.isOk) {
  console.log('Incremented successfully');
  console.log(incrementReceipt.value.value); // inner value of the `ok`
}

Or, you can use txErr to expect an err type. This is helpful for testing your function's validation logic.

In the increment function, an error is returned if the step is greater than 5. You can test this like so:

const incrementReceipt = txErr(counter.increment(6n), alice);
expect(incrementReceipt.value).toEqual(100n); // the "inner" value of the `(err)`

Getting variables and map entries

Use varGet and mapGet to get the value of a variable or map entry.

const contractId = counter.identifier;
const currentValue = varGet(contractId, counter.variables.counter);
expect(currentValue).toEqual(2n);
 
const lastIncrement = mapGet(contractId, counter.maps.lastIncrement, alice); // the type is `bigint | null`
expect(lastIncrement).toEqual(1n);
 
const bobIncrement = mapGet(contractId, counter.maps.lastIncrement, accounts.wallet_2.address);
expect(bobIncrement).toBeNull();

Handling transaction events

Use filterEvents to get the events emitted by a transaction. This is useful for testing that your contract emits the correct events.

import { CoreNodeEventType, cvToValue } from '@clarigen/core';
 
const incrementReceipt = txOk(counter.increment(3n), alice);
const printEvents = filterEvents(incrementReceipt.events, CoreNodeEventType.ContractEvent);
expect(printEvents.length).toEqual(1);
const [print] = printEvents;
 
// next, use `cvToValue` to easily convert from a ClarityValue
// to a "JS Native" value
const printData = cvToValue<{
  action: string;
  object: string;
  value: bigint;
}>(print.data.value);
 
expect(printData).toEqual({
  object: 'counter',
  action: 'incremented',
  value: 5n,
});