blockchainsv
Developer Tools & Infrastructure·June 20, 2026·12 min read

Choose Foundry or Hardhat for Solidity Unit Testing

We've all been there. You're deep into a critical integration, trying to simulate a devious reentrancy attack in your test suite, and the whole process grinds to a halt.

Choose Foundry or Hardhat for Solidity Unit Testing

You've Hit a Wall: Your Tests Are Flaky, Slow, and You're Tired of Wrestling with JavaScript for Solidity Logic

The choice of your testing framework isn't a minor preference—it's a foundational architectural decision that shapes your development velocity, the types of bugs you can catch, and your team's daily sanity. Let's break down the two titans of the Solidity testing world: Foundry and Hardhat. We're not here to declare a winner, but to map the trade-offs so you can choose the right tool for your project's unique journey.

Architectural Divergence: Rust-Powered Forge vs. Node.js Flexibility

At its core, this is a debate between two fundamentally different philosophies.

Foundry is built from the ground up in Rust. Its command-line tool, Forge, executes tests written directly in Solidity. This isn't just a stylistic choice—it's a performance revolution. Because there's no JavaScript interpreter in the loop, test execution is often 10x to 100x faster than JS-based frameworks for complex suites. If your project has hundreds of test cases that each deploy multiple contracts and run through lengthy state transitions, that speed difference turns a five-minute test run into a thirty-second one. The development experience is terminal-first. You write your contract code, you write your test code, and you run forge test. The entire mental model is unified around the EVM and Solidity. There is no intermediary language, no serialization layer, no async/await ceremony—just direct interaction with the virtual machine.

Hardhat is a pillar of the JavaScript/TypeScript ecosystem, built on Node.js. It leverages libraries like Ethers.js, Chai, and Mocha that frontend and backend Web2 developers already know intimately. Its power lies in its vast, mature plugin ecosystem. Need to deploy to five testnets with different configurations? There's a plugin. Want a gas report for every test run? There's a plugin. This ecosystem turns Hardhat into a customizable swiss-army knife for the entire development lifecycle, not just testing. The architecture reflects a belief that smart contract development doesn't happen in isolation—it happens inside broader application stacks that speak JavaScript fluently.

Foundry's architecture lets you think in pure EVM terms—write a test in the same language you write the contract, and the tool doesn't get in your way.

The performance gap isn't theoretical. Foundry runs its own EVM implementation called revm, written in Rust, which compiles to machine code. Hardhat runs on Hardhat Network, a JavaScript-based EVM that is fast for a JS implementation but fundamentally constrained by the V8 engine's overhead. For small projects with a few dozen tests, the difference is negligible. For enterprise-grade protocols with thousands of test cases, the divergence becomes a real factor in daily workflow.

Testing Paradigms: Writing Solidity-Native Tests vs. JavaScript/TypeScript Suites

This is where the daily workflow diverges most sharply.

With Foundry, your test file is just another Solidity contract. You import your contract, write a setUp() function to deploy it, and write test functions prefixed with test. Assertions are made with simple require statements or the framework's own assertion library. It feels immediate. The language barrier is zero—you're already writing Solidity. There is no context-switch cost when you need to understand what a test is doing, because the test code and the contract code share the same syntax, the same type system, and the same mental model.

// Inside test/MyContract.t.sol
function testDeposit() public {
myContract.deposit{value: 1 ether}();
assertEq(myContract.balances(address(this)), 1 ether);
}

With Hardhat, you write tests in JavaScript or TypeScript using Mocha's describe and it blocks. You connect to your deployed contracts via Ethers.js and use Chai for assertions. The context switches between Solidity and JS are constant but can be powerful for certain tasks, like complex data parsing or integrating with off-chain services in your tests.

// Inside test/MyContract.test.js
it("Should update balance on deposit", async function () {
await myContract.deposit({ value: ethers.utils.parseEther("1") });
const balance = await myContract.balances(owner.address);
expect(balance).to.equal(ethers.utils.parseEther("1"));
});

The trade-off here is between unity of language (Foundry) and familiarity with a massive general-purpose language (Hardhat). For a Solidity developer, Foundry's tests often feel more concise and direct. For a full-stack team, Hardhat's tests might be easier to integrate into a broader JavaScript testing suite.

There's another practical dimension: readability of failure messages. Hardhat's Chai integration produces rich, human-readable assertion failures out of the box—"expected 999999 to equal 1000000." Foundry's default assertion messages are more terse, though you can pass custom error strings to assertEq. For teams that spend significant time debugging failing CI pipelines, the richness of failure output matters more than most developers initially expect.

Advanced State Manipulation: Leveraging Foundry Cheatcodes for Complex Scenarios

This is where Foundry truly shines for deep, protocol-level debugging. Its "cheatcodes" are pre-deployed contracts that let you manipulate the EVM's state during tests in ways that are otherwise cumbersome or impossible.

  • vm.prank(address): Executes the next call as if it came from a specified address. Crucial for testing permissioned functions without deploying mock caller contracts.
  • vm.startPrank(address): The persistent variant—every subsequent call behaves as if it originates from the spoofed address until you call vm.stopPrank(). Useful when testing multi-step workflows that require a specific caller context throughout.
  • vm.warp(timestamp): Sets the block.timestamp to a specific value. Essential for testing time-dependent logic like vesting schedules, governance voting periods, or interest accrual.
  • vm.roll(blockNumber): Sets the block.number. Perfect for testing forked historical data or simulating the passage of many blocks.
  • vm.store(slot, value): Directly writes to a contract's storage slot. The nuclear option for bypassing access controls in a test to set up a specific state without going through the contract's public interface.
  • vm.deal(address, amount): Instantly funds an address with ETH, removing the need for complex coinbase mining workarounds.
  • vm.expectRevert(): Asserts that the next external call reverts, with optional checks on the revert reason. Clean, declarative error-path testing.

In Hardhat, achieving the same requires more boilerplate. You'd need to use Hardhat Network's JSON-RPC methods like evm_setNextBlockTimestamp or evm_mine, and for storage manipulation, you'd typically have to build a helper function or use a plugin. Hardhat does have hardhat-network-helpers which provides time.increaseTo() and mine() utilities—these are good, but they feel like wrappers around the EVM rather than native extensions of it.

When you need to simulate a hack that involves a flash loan, a price oracle manipulation, and a governance takeover in the same transaction, Foundry's cheatcodes are the difference between a 50-line test and a 500-line test.

This matters most in security auditing and protocol development. When you're trying to reproduce an exploit path that involves impersonating multiple addresses, jumping across time, and manipulating balances in the same transaction sequence, Foundry's cheatcodes compose naturally in a single Solidity function. In Hardhat, the equivalent test often sprawls across multiple beforeEach blocks, helper functions, and manual evm_snapshot/evm_revert calls.

Ecosystem Maturity and Plugin Integration for Deployment Workflows

While Foundry is catching up with forge-deploy and other tools, Hardhat's plugin ecosystem is its undisputed stronghold. This isn't about "bells and whistles"—it's about production-grade workflows that save hours every week.

  • Verification: The @nomiclabs/hardhat-etherscan plugin makes verifying contracts on block explorers a one-command process. With Foundry, you currently rely on separate tools like foundry-verify or manual API calls to Etherscan.
  • Deployment Management: Plugins like hardhat-deploy manage deployment artifacts, allow for deterministic deployments (via CREATE2 salts), and simplify multi-network rollouts. You can define deployment steps as named functions that run in order, skip already-deployed contracts, and export addresses automatically. This is a lifesaver for complex projects that deploy across Ethereum mainnet, multiple L2s, and testnets.
  • Gas Reporting & Coverage: Plugins like hardhat-gas-reporter and solidity-coverage integrate directly into your test run, providing actionable metrics without extra setup. hardhat-gas-reporter even shows per-method gas costs in a clean table, which is invaluable during optimization passes.
  • TypeScript Type Generation: typechain generates TypeScript bindings for your contracts automatically, giving your tests full autocompletion and type safety. Foundry has forge bind now, but the TypeChain ecosystem has years of production usage behind it.
  • Account Management: hardhat-ledger and hardhat-mnemonic plugins handle hardware wallet signing and HD wallet derivation for deployment, critical for teams that store mainnet keys on hardware devices.

For a team that values a fully integrated, GUI-friendly dashboard for every aspect of the contract lifecycle, Hardhat's ecosystem can feel like a complete IDE. Foundry, in contrast, feels like a set of precision, high-performance command-line tools. The choice often comes down to: do you want a fully-featured workshop (Hardhat) or a set of razor-sharp, focused instruments (Foundry)?

It's worth noting that Foundry's ecosystem is growing rapidly. Tools like chisel (an interactive Solidity REPL) and cast (a CLI for interacting with contracts) fill gaps that previously required external tooling. The gap is narrowing, but today, Hardhat still leads on breadth of integration.

Native Fuzzing and Invariant Testing Capabilities in Modern Development

Both frameworks now support property-based testing, but their integration differs.

Foundry has fuzz testing baked into its core. Any test function that accepts input arguments automatically becomes a fuzz test—Forge generates random inputs to try and break your assertions. You write function testFuzz_Deposit(uint256 amount) and Forge runs it hundreds of times with random values, automatically shrinking failing inputs down to the minimal reproduction case. Its invariant testing (invariant_* functions) is equally first-class. You define properties that should always hold true—like "total supply equals sum of all balances"—and the fuzzer bombards a suite of contracts with sequences of random calls to try to violate them.

The key power of Foundry's invariant testing is the concept of "call sequences." Forge doesn't just call individual functions with random arguments—it generates random sequences of function calls, interleaved across multiple contracts, and checks invariants after each sequence. This is the kind of testing that catches bugs where two seemingly safe operations, when combined in an unexpected order, produce a broken state. It's the closest thing to automated exploit hunting that exists in a general testing framework.

Hardhat can also perform fuzz testing, but it typically requires integrating an external tool or library. The hardhat-fuzz plugin or using a library like fast-check within your Mocha tests are common paths. The integration, while possible, is not as seamless as Foundry's "just add an argument" approach. Setting up invariant testing in Hardhat often means writing custom test harnesses that deploy contracts, generate call sequences manually, and check invariants in loop—doable, but with significantly more boilerplate.

For a security-focused team, this native fuzzing capability is a major point in Foundry's favor. It lowers the friction to writing comprehensive, property-based tests that catch edge cases a human tester would never imagine. When a fuzz test discovers that your vault contract breaks at type(uint256).max - 1, you'll be glad you wrote it.

Let's compare the core offerings side-by-side:

AspectFoundryHardhat
LanguageRust (Core), Solidity (Tests)JavaScript/TypeScript (Tests)
Execution SpeedVery High (Rust, native EVM)Moderate (Node.js)
State ManipulationNative Cheatcodes (Solidity API)Via JSON-RPC methods & plugins
Testing StyleSolidity-native, unifiedJS/TS, Mocha/Chai/Ethers.js
Ecosystem & PluginsGrowing, focused on coreMassive, mature, all-encompassing
Fuzz/Invariant TestingFirst-class, native integrationPossible, requires plugins/tools
Deployment Toolingforge-deploy, scriptshardhat-deploy, rich plugin set
Contract VerificationExternal tools or manualOne-command via plugin
TypeScript Bindingsforge bindTypeChain (mature)

Making the Call: It's About Your Team's Neurology, Not Just Technology

So, how do you choose? We need to look at our team, our project, and the friction points we hate the most.

Choose Foundry if:

  • Your team lives and breathes Solidity. The thought of context-switching to JS for tests is a real productivity tax.
  • You prioritize raw execution speed above all else. Long test suites are a daily bottleneck and you've already optimized everything else.
  • You work on complex DeFi protocols where deep state manipulation and robust fuzz testing are non-negotiable for security audits.
  • You prefer a lean, terminal-driven workflow and are comfortable without an all-in-one GUI dashboard.
  • You want your tests to serve as documentation—Solidity tests are readable by anyone who can read the contract itself.

Choose Hardhat if:

  • Your team is polyglot, with strong JavaScript/TypeScript expertise. The familiarity lowers the onboarding cost for new developers joining the project.
  • You need a mature, out-of-the-box workflow for deployment, verification, and reporting today. The plugin ecosystem is a huge accelerator that can't be replicated overnight.
  • Your project involves heavy interaction with off-chain services in tests, where JavaScript's flexibility and its npm ecosystem are genuine advantages.
  • You value a comprehensive, integrated environment that extends beyond pure testing into deployment management and monitoring.
  • Your smart contracts are part of a larger dApp where the frontend and backend are already in JavaScript/TypeScript—shared tooling reduces context-switching across the entire stack.
The right testing framework is the one that disappears—where the friction between your idea and its verification approaches zero, and you stop thinking about the tool entirely.

Use both. There's no rule that says you must pick one forever. Some teams write unit tests and fuzz tests in Foundry for speed and security, then use Hardhat for deployment scripts, verification, and integration testing against external services. The tools don't compete—they complement.

Start by cloning a boilerplate for each. Write the same test suite in both Foundry and Hardhat. Spend a day with each. The one that feels less like a tool and more like an extension of your thought process—that's your winner. Let's build.

FAQ

Why is Foundry faster than Hardhat?
Foundry is built in Rust and executes tests directly in Solidity without a JavaScript interpreter, whereas Hardhat runs on Node.js and is constrained by the V8 engine's overhead.
Can I use Hardhat if I need to perform fuzz testing?
Yes, but it requires integrating external tools or plugins like hardhat-fuzz or fast-check, as it is not a native, seamless feature like it is in Foundry.
What are Foundry cheatcodes?
Cheatcodes are pre-deployed contracts that allow developers to manipulate the EVM state during tests, such as spoofing addresses, warping time, or directly modifying storage slots.
Which framework is better for teams with strong JavaScript expertise?
Hardhat is generally better suited for teams with deep JavaScript or TypeScript knowledge, as it leverages familiar libraries like Ethers.js, Chai, and Mocha.
Does Foundry support contract verification?
Foundry currently relies on separate tools or manual API calls for contract verification, whereas Hardhat offers one-command verification through its plugin ecosystem.

By Chloe Redfern