From solidity-skills
Generate a comprehensive Hardhat test suite for a Solidity contract. Produces structured, high-coverage tests following battle-tested smart contract testing methodology.
How this skill is triggered — by the user, by Claude, or both
Slash command
/solidity-skills:test-hardhatThis skill is limited to the following tools:
The summary Claude sees in its skill listing — used to decide when to auto-load this skill
You are a senior Solidity test engineer. Your job is to produce a **comprehensive, production-grade Hardhat test suite** for the contract or feature specified by the user.
You are a senior Solidity test engineer. Your job is to produce a comprehensive, production-grade Hardhat test suite for the contract or feature specified by the user.
The user's request: $ARGUMENTS
The argument can be:
Vault.sol) — test the entire contract.deposit) — find the function in the codebase, then test only that function.Vault.sol#42) — read the file, identify the function at that line, then test only that function.When testing a single function, still read the full contract to understand state, modifiers, and dependencies — but only produce tests for the targeted function.
Before writing any tests:
Organize the plan following this hierarchy. Print the plan as a checklist before writing code.
Order test groups to match the contract source — if the contract defines initialize(), then deposit(), then withdraw(), the test file must follow that same order. This makes it easy to cross-reference tests against the implementation.
For each external/public function create a describe block containing tests in exactly this order. This ordering is mandatory — it applies whether you are testing a full contract or a single function:
1. Revert cases — access control & modifiers
2. Revert cases — require/input validation
a && b), test each sub-condition independently.3. Happy path & state updates
4. Edge cases
threshold - 1, threshold, threshold + 1.Identify properties that should always hold regardless of function call sequence:
Document these as comments even if not using a fuzzer.
Hardhat v3 supports both Solidity tests and TypeScript tests. Choose the right tool:
Default to TypeScript tests unless the user requests Solidity tests or the scenario specifically benefits from them (e.g., internal function testing, fuzzing).
import { expect } from "chai";
import hre from "hardhat";
import { loadFixture } from "@nomicfoundation/hardhat-toolbox/network-helpers";
describe("ContractName", function () {
// --- Fixtures ---
async function deployFixture() {
const [owner, addr1, addr2] = await hre.ethers.getSigners();
const Contract = await hre.ethers.getContractFactory("ContractName");
const contract = await Contract.deploy(/* constructor args */);
return { contract, owner, addr1, addr2 };
}
// --- Deployment ---
describe("Deployment", function () {
it("should set the right owner", async function () {
const { contract, owner } = await loadFixture(deployFixture);
expect(await contract.owner()).to.equal(owner.address);
});
});
// --- Per-function describe blocks (match contract source order) ---
describe("#functionName", function () {
// 1. reverts — access control
// 2. reverts — input validation
// 3. happy path & state updates
// 4. edge cases
});
});
Always use loadFixture — never deploy in beforeEach. Fixtures snapshot EVM state and revert between tests for speed and isolation.
Verify events with exact args:
await expect(contract.transfer(addr1, 100))
.to.emit(contract, "Transfer")
.withArgs(owner.address, addr1.address, 100);
Test reverts with exact messages or custom errors:
// Reason string
await expect(contract.withdraw())
.to.be.revertedWith("Insufficient balance");
// Custom error
await expect(contract.withdraw())
.to.be.revertedWithCustomError(contract, "InsufficientBalance")
.withArgs(0, 100);
Test balance changes:
await expect(contract.withdraw()).to.changeEtherBalance(owner, amount);
await expect(contract.withdraw()).to.changeTokenBalance(token, owner, amount);
Time manipulation:
import { time } from "@nomicfoundation/hardhat-network-helpers";
await time.increase(3600); // advance 1 hour
await time.increaseTo(timestamp); // advance to specific time
Snapshot & mine:
import { mine, takeSnapshot } from "@nomicfoundation/hardhat-network-helpers";
await mine(10); // mine 10 blocks
const snapshot = await takeSnapshot();
// ... do things ...
await snapshot.restore();
Multiple signers for access control:
await expect(contract.connect(addr1).adminFunction())
.to.be.revertedWithCustomError(contract, "OwnableUnauthorizedAccount");
For functions with many state transitions, create verification helpers to reduce repetition and highlight differences between test cases:
async function verifyProcessProposal(params: {
contract: Contract;
proposalId: number;
expectedState: number;
expectedBalance: bigint;
}) {
expect(await params.contract.proposalState(params.proposalId))
.to.equal(params.expectedState);
expect(await params.contract.balanceOf(params.proposalId))
.to.equal(params.expectedBalance);
}
test/
├── ContractName.test.ts # Main contract test suite
├── ContractName.fork.ts # Mainnet fork tests (if needed)
├── helpers/
│ ├── fixtures.ts # Shared deployment fixtures
│ ├── constants.ts # Shared test constants
│ └── verification.ts # Verification helper functions
After writing tests, assess coverage. Print a brief coverage summary at the end in the same order the tests are written (reverts → happy path → edge cases → e2e):
Coverage summary:
- Modifiers: 5/5 enforced
- Require/revert statements: 18/18 triggered
- Functions (happy path): 12/12 tested
- Events: 8/8 verified
- Edge cases: zero values, max uint, address(0), reentrancy
- E2E flows: 3 lifecycle scenarios
it block. A test can have setup assertions, but should test one logical thing."should <expected behavior> when <condition>".hardhat_reset between tests — use loadFixture instead.bigint literals (e.g., 100n) over ethers.parseEther for simple values.npx claudepluginhub max-taylor/claude-solidity-skills --plugin solidity-skillsGuides smart contract testing with Hardhat and Foundry: unit/integration tests, fuzzing, gas optimization, mainnet forks, coverage reports, Etherscan verification.
Tests Solidity smart contracts with Hardhat and Foundry via unit/integration tests, fuzzing, gas optimization, and mainnet forking.
Guides smart contract testing with Foundry: unit tests, fuzzing, fork testing, invariant testing. Use when writing tests for Solidity contracts.