Learn how to write and run integration tests for your Clarity smart contracts using the Clarinet JS SDK and Vitest.
Integration testing is a crucial step in smart contract development that involves testing how different components of your system work together. The Clarinet JS SDK provides powerful tools for writing and running integration tests, allowing you to simulate complex scenarios and interactions between multiple contracts.
By using integration tests, you can ensure that your smart contracts function correctly as part of a larger system and catch potential issues that might not be apparent in unit tests alone.
Start by creating a new Clarinet project. This command will create a new directory named defi and set up a basic Clarinet project inside it.
After changing into your project directory, run npm install to install the package dependencies for testing.
We are going to use the same defi contract that we used in the unit testing guide, but with some additional functionality - the ability to borrow STX from the contract. If you don't have this project setup already, follow the steps below:
Then, inside your defi.clar file, copy and paste the following contract code:
Run clarinet check to ensure that your smart contract is valid and ready for testing.
You can find the full code for this project in this repo.
In order to borrow STX from the contract, users must first deposit STX into it. Therefore, we need to write an integration test that simulates the interaction between these two functions.
Inside of your defi.test.ts file, add the following test:
In this integration test, we're simulating a scenario where a user deposits STX into our DeFi contract and then borrows against that deposit. Let's walk through the process step by step.
We start by simulating a deposit of 1000 STX from wallet1. To do this, we use the simnet.callPublicFn() method, which allows us to call public functions in our smart contract just as we would on the actual blockchain.
After making the deposit, we want to verify that it was successful. We do this by checking the total deposits in the contract using simnet.getDataVar().
This handy method lets us peek at the value of data variables defined in our contract.
To learn more about available methods for integration testing, check out the reference page.
To ensure the deposit was recorded correctly, we use a custom matcher, toBeUint. This matcher is specifically designed to check if a value is a Clarity unsigned integer with the exact value we expect.
With the deposit confirmed, we simulate wallet1 borrowing 10 STX. We do this with another call to simnet.callPublicFn(), this time invoking the borrow function of our contract.
After the borrowing operation, we want to check how much wallet1 owes. We use simnet.callReadOnlyFn() to call a read-only function named get-amount-owed in our contract.
Finally, we verify the amount owed using another custom matcher, toBeOk(Cl.uint(10)). This matcher is particularly useful because it checks two things at once: it ensures that our contract returned a successful Clarity response type and that the value returned is a Clarity unsigned integer with the exact value we expect (10).
These custom matchers and simnet methods are powerful tools in our testing arsenal. They allow us to simulate complex interactions with our smart contracts and make detailed assertions about the results.