0x03: Test Smart Contracts with Hardhat: Full Tutorial Using Mocha & Chai
0x03: Test Smart Contracts with Hardhat: Full Tutorial Using Mocha & Chai

0x03: Test Smart Contracts with Hardhat: Full Tutorial Using Mocha & Chai

Test Smart Contracts with Hardhat : When we write smart contracts, it’s not enough to just deploy and hope everything works. In the world of blockchain, a small bug can lead to massive losses, so testing smart contracts is one of the most important skills for a Solidity developer. In this tutorial, I’ll walk you step-by-step on how to test smart contracts using Hardhat, Mocha, and Chai, with live examples and clear explanations.

Why Testing Smart Contracts Is Crucial?

Why Testing Smart Contracts Is Crucial?
Why Testing Smart Contracts Is Crucial?

Unlike traditional apps, smart contracts are immutable once deployed. That means no quick hotfixes if something goes wrong. If your function doesn’t handle edge cases or if a math calculation fails, the bug lives forever on-chain.

Here’s why testing matters:

  1. Prevent financial loss – A vulnerable contract can drain funds in seconds.
  2. Confidence before deployment – Testing ensures your logic matches the requirements.
  3. Catch regressions early – When you modify code later, tests confirm nothing else broke.
  4. Professional credibility – Writing well-tested contracts makes you a reliable developer.

Now that you understand why, let’s get into the tools we’ll use.

Setting Up Hardhat

Setting Up Hardhat
Setting Up Hardhat

Hardhat is one of the most popular frameworks for Ethereum development. It helps you write, compile, deploy, and test Solidity contracts with ease.

To start:

Select “Create a JavaScript project” and you’ll get a boilerplate project.

For testing, install ethers.js and chai:

This package already includes ethers.js, chai, and useful matchers for testing.

Writing Our First Smart Contract

Writing Our First Smart Contract
Writing Our First Smart Contract

Let’s make a simple Counter.sol contract to test.

This simple contract has:

  • A count state variable
  • increment and decrement functions
  • Events for changes
  • A require statement to avoid going negative

Perfect for testing!

Writing Test Cases with Mocha and Chai

Writing Test Cases with Mocha and Chai
Writing Test Cases with Mocha and Chai

Mocha is the test runner, and Chai provides the assertions. With Hardhat, these integrate smoothly.

Create a test file:

Now open Counter.js and let’s write our first test.

Using describe, it, and beforeEach

A common pattern is:

  • describe → Groups tests logically
  • beforeEach → Runs before each test (e.g., deploy fresh contract)
  • it → Defines an actual test case

Here’s how:

This first test checks the default value of our counter.

Positive Test Scenarios

Now let’s test what should happen in normal conditions.

Here, we verify that incrementing and decrementing update the value as expected.

Negative Test Scenarios

Testing only the happy path is risky. What if someone tries to decrement before increment? That should fail.

This ensures our require condition is working.

Testing Events

Events are crucial for blockchain apps because off-chain services rely on them. Let’s test if they’re emitted properly.

Now we’re sure that our events fire correctly.

Using Ethers.js in Tests

Using Ethers.js in Tests
Using Ethers.js in Tests

Hardhat integrates ethers.js so you can interact with contracts naturally.

Example: Testing function calls and signers.

With .connect(signer), you can simulate calls from different accounts. This becomes powerful when testing access control or multi-user interactions.

Structuring Tests Like a Pro

A well-organized test suite makes debugging much easier. Best practices include:

  • Group similar tests with describe.
  • Use beforeEach to avoid duplicate deployment code.
  • Keep tests focused: one it block should test only one scenario.
  • Write both positive and negative cases.

A clean structure looks like this:

This hierarchy makes your tests readable and scalable.

Running Tests

Simply run:

You’ll see output showing which tests passed and which failed. A green check means you’re good to go!

Debugging Failed Tests

Debugging Failed Tests
Debugging Failed Tests

When a test fails, Hardhat gives you detailed error messages. Common issues include:

  • Assertion error → Expected vs actual value mismatch.
  • Reverted transaction → Missing await or wrong condition.
  • Wrong event args → Double-check emitted values.

Use console.log inside tests or Solidity contracts for quick debugging.

Expanding to Real-World Scenarios

Our Counter contract is basic, but the same principles apply to complex contracts. Examples:

  • ERC20 tokens → Test transfers, approvals, and events.
  • NFT contracts → Test minting, burning, metadata, ownership.
  • DeFi contracts → Test deposits, withdrawals, rewards, and failures.

Once you master testing with Hardhat, Mocha, and Chai, you can scale this approach to any project.

Common Pitfalls in Solidity Testing

  1. Forgetting await → Without it, your tests may pass incorrectly.
  2. Testing too much in one case → Split scenarios into smaller tests.
  3. Ignoring edge cases → Attackers won’t ignore them.
  4. Not testing events → DApps rely heavily on them.

Last But Not Least..

We just covered a full workflow to test smart contracts with Hardhat, Mocha, and Chai. From setting up the environment to writing positive/negative scenarios and checking events, you now have the confidence to write professional test suites.

To recap:

  • Use describe, it, beforeEach for clean structure.
  • Write both success and failure tests.
  • Test events, exceptions, and edge cases.
  • Use ethers.js for interacting with contracts.

With consistent practice, you’ll avoid costly bugs and build trust as a Solidity developer.

FAQs

Q1. Can I use Hardhat with TypeScript for testing?

Yes! Hardhat supports TypeScript out of the box. Many developers prefer TypeScript for type safety.

Q2. Is Mocha/Chai better than Truffle’s testing framework?

Hardhat with Mocha/Chai is faster, more flexible, and integrates with ethers.js easily. Truffle is still valid, but Hardhat is now industry standard.

Q3. Do I need to test every single function?

Yes, ideally. Every public function should have at least one positive and one negative test.

Q4. Can I simulate multiple accounts in tests?

Absolutely! Hardhat provides multiple signers via ethers.getSigners().

Q5. How do I test gas usage?

You can measure gas costs by checking transaction receipts in tests.

Spread the love
Leave a Comment

Comments

No comments yet. Why don’t you start the discussion?

Leave a Reply

Your email address will not be published. Required fields are marked *