Testing
Writing Unit Tests
Unit tests are essential to ensure your smart contract behaves correctly. Using Mocha and Chai in conjunction with Hardhat provides a robust framework for testing your Solidity contracts.
Install Mocha/Chai
To set up testing, install the required libraries:
npm install --save-dev mocha chai
Example Test Suite: Token Contract
Here's an improved example of a test suite for a token contract:
const { expect } = require("chai");
describe("Token Contract", function () {
let Token, token, owner, addr1, addr2;
// Before each test, deploy a new token contract instance
beforeEach(async function () {
[owner, addr1, addr2] = await ethers.getSigners(); // Retrieve test accounts
Token = await ethers.getContractFactory("MyToken");
token = await Token.deploy(); // Deploy the contract
});
describe("Deployment", function () {
it("Should set the correct owner", async function () {
expect(await token.owner()).to.equal(owner.address);
});
it("Should assign the total supply of tokens to the owner", async function () {
const ownerBalance = await token.balanceOf(owner.address);
expect(await token.totalSupply()).to.equal(ownerBalance);
});
});
describe("Transactions", function () {
it("Should transfer tokens between accounts", async function () {
// Transfer 50 tokens from owner to addr1
await token.transfer(addr1.address, 50);
const addr1Balance = await token.balanceOf(addr1.address);
expect(addr1Balance).to.equal(50);
});
it("Should fail if sender doesn’t have enough tokens", async function () {
const initialOwnerBalance = await token.balanceOf(owner.address);
// Attempt to transfer 1 token from addr1 (has 0 tokens) to addr2
await expect(
token.connect(addr1).transfer(addr2.address, 1)
).to.be.revertedWith("Insufficient balance");
// Ensure owner's balance remains unchanged
expect(await token.balanceOf(owner.address)).to.equal(initialOwnerBalance);
});
it("Should update balances after transfers", async function () {
const initialOwnerBalance = await token.balanceOf(owner.address);
// Transfer 100 tokens from owner to addr1
await token.transfer(addr1.address, 100);
// Transfer 50 tokens from addr1 to addr2
await token.connect(addr1).transfer(addr2.address, 50);
const finalOwnerBalance = await token.balanceOf(owner.address);
expect(finalOwnerBalance).to.equal(initialOwnerBalance - 100);
const addr1Balance = await token.balanceOf(addr1.address);
expect(addr1Balance).to.equal(50);
const addr2Balance = await token.balanceOf(addr2.address);
expect(addr2Balance).to.equal(50);
});
});
});
Breakdown of the Test Cases:
Deployment Tests:
Ownership: Verifies that the contract's owner is correctly assigned upon deployment.
Initial Token Distribution: Confirms that the contract assigns the total token supply to the owner's balance.
Transaction Tests:
Successful Transfers: Verifies that tokens can be transferred from one account to another and the balances are updated correctly.
Failed Transfers: Ensures that a transfer will fail if the sender doesn't have enough tokens, reverting the transaction.
Balance Updates: Checks that the balances are correctly updated after multiple transactions.
Running the Tests
To run the test suite, use the following command:
npx hardhat test
This will execute all test cases and display the results, ensuring that your contract behaves as expected under various conditions.
Improvements in this Version:
Modular Tests: Divided tests into logical groups (e.g.,
Deployment
andTransactions
) for better organization and readability.Edge Case Testing: Added test cases for failure conditions (like insufficient balances) to ensure that your contract handles errors correctly.
Reusability: Used
beforeEach
to deploy a fresh contract instance for each test, ensuring isolation between test cases and preventing state leakage.
This structure makes the tests more maintainable and easier to extend as your contract grows.