Recommended resources
The freely available resources this lesson will direct you to.
Hello! Welcome to your third lesson in Module 2: "Modern Development & Testing Toolchain."
Introduction
In the past two lessons, you've had hands-on experience setting up complete development environments with both Hardhat (using TypeScript) and Foundry (using Solidity). You've compiled, tested, and deployed contracts in both frameworks.
Today, we will step back from implementation and focus on strategy. This lesson is dedicated to comparing the fundamental philosophies and practical workflows of Hardhat and Foundry. The goal is not to declare a "winner," but to understand the trade-offs so you can choose the right tool for the right job. Given your extensive background in front-end development, you have a valuable perspective on the JavaScript/TypeScript ecosystem that Hardhat embraces, which we will contrast with Foundry's Solidity-native approach.
Learning Outcome: By the end of this lesson, you will be able to compare the testing philosophies and workflows of Foundry (Solidity-scripted) and Hardhat (JavaScript/TypeScript-scripted).
1. The Core Philosophical Divide: Language and Ecosystem
At its heart, the choice between Hardhat and Foundry is a choice of development philosophy. Do you leverage the vast, mature JavaScript ecosystem, or do you stay as close to the EVM as possible by working entirely in Solidity?
Let's start with a high-level video that frames this choice.
Hardhat vs Foundry | Solidity Frameworks Compared
This video, 'Hardhat vs Foundry', provides a concise overview of the two frameworks, their origins, and the primary factors that influence a developer's choice between them.
Please watch from 00:27 to 01:18 and then from 02:46 to 03:56. These segments cover: The fundamental difference: Hardhat as a JavaScript framework and Foundry as a Rust-based toolkit for Solidity testing. How a developer's background (e.g., web dev vs. security auditor) and language preference often guide the choice.
As the video highlights, the decision is often framed by your starting point:
-
Hardhat (JavaScript/TypeScript-centric):
- Philosophy: Integrate smart contract development into the powerful and familiar JavaScript ecosystem.
- Workflow: Write Solidity, then switch to JS/TS to write tests and deployment scripts. This is a natural fit for full-stack and front-end developers, as testing logic can closely mirror front-end interaction logic using libraries like
ethers.js. - Pros: Massive ecosystem of plugins and libraries (via npm), familiar testing patterns (
describe/it), and seamless code-sharing with your dApp's front-end. - Cons: Requires context-switching between languages. The test environment is a JS-based simulation of the EVM, which can sometimes introduce subtle differences or overhead.
-
Foundry (Solidity-centric):
- Philosophy: Write, test, and script in Solidity. Minimize context switching and work in an environment that is as close to the production EVM as possible.
- Workflow: The entire backend development lifecycle happens within Solidity. Tests are contracts, and scripts are contracts.
- Pros: No context switching, extremely high performance, and tests that run in a native Solidity environment. This appeals to Solidity purists and security researchers.
- Cons: The testing paradigm (using "cheat codes") has a learning curve. The ecosystem of plugins is smaller than Hardhat's.
2. A Practical Comparison of Testing Workflows
Let's move from philosophy to practice. How do these differences manifest when you're actually writing tests?
The following article provides excellent side-by-side code examples and analysis.
Smart Contract Frameworks - Foundry vs Hardhat
The article 'Smart Contract Frameworks - Foundry vs Hardhat' by Chainstack offers a detailed, code-level comparison of the two frameworks. We'll focus on the sections that directly compare the testing experience.
Please read the sections titled 'Writing tests' and 'Understanding Foundry cheats for testing'. In 'Writing tests', pay close attention to the code snippets for MiniBank.t.sol (Foundry) and the JavaScript test file (Hardhat). Notice the structural differences. In 'Understanding Foundry cheats for testing', focus on how Foundry uses a special vm object to manipulate the blockchain state, which is a key differentiator.
Let's break down the key differences you observed in the article.
Test Structure and Assertions
- Hardhat: Uses the BDD (Behavior-Driven Development) style with
describe()andit()blocks, which is standard in the JavaScript world. Assertions are typically made with the Chai library, providing a fluent, readable syntax likeexpect(value).to.equal(5). - Foundry: A test file is itself a Solidity contract that inherits from
DSTest(orTestinforge-std). Each test is a public function prefixed withtest. Assertions are direct function calls likeassertEq(value, 5).
Manipulating Blockchain State (The "Magic")
This is one of the most significant workflow differences.
- Hardhat: State manipulation is done by sending special JSON-RPC requests to the Hardhat Network. For example, to impersonate an account, you use
hardhat_impersonateAccount. These are powerful but feel like external commands sent to the node. - Foundry: Uses "cheat codes". As you saw in the article, these are special functions accessed via a
vminstance (Vm(HEVM_ADDRESS)). They allow you to directly and natively manipulate the EVM state from within your Solidity test code. Functions likevm.prank(address)(setmsg.senderfor the next call) andvm.warp(timestamp)(setblock.timestamp) are first-class citizens.
Testing Reverts and Events
Let's watch a video that demonstrates how Foundry's cheat codes handle two of the most common testing tasks: checking for reverts and verifying event emissions.
Solidity Development with Foundry: Cast, Anvil, Chisel, and Forge
This clip from the Ethereum Engineering Group provides a clear demonstration of Foundry's cheat codes for handling failure conditions and events.
Watch from 25:18 to 33:43. This segment covers: Testing for Reverts: How to use testFail and, more precisely, vm.expectRevert() to assert that a specific error occurs. Testing for Events: The workflow for using vm.expectEmit() to verify that the correct event was emitted with the correct parameters. Impersonation: A practical example of using vm.prank() to test access control logic.
As you can see, the syntax can be quite different:
- Hardhat:
await expect(contract.failingFunction()).to.be.revertedWith("Error")is a single, fluent line. - Foundry:
vm.expectRevert("Error"); contract.failingFunction();is a two-step process: you declare the expected revert before the call that causes it. - The event testing syntax in Foundry is notably more verbose than Hardhat's
expect().to.emit().withArgs(), requiring you to define which parameters to check and then emit a matching event in your test.
3. Performance and Advanced Capabilities
Why would developers embrace Foundry's steeper learning curve for cheat codes? The primary answer is performance and the capabilities it unlocks.
Raw Speed
Foundry is written in Rust and executes tests in a highly optimized, EVM-native environment. Hardhat, running on Node.js, has inherent overhead.
The Chainstack article you read earlier provides concrete numbers.
Smart Contract Frameworks - Foundry vs Hardhat
Let's revisit the Chainstack article to look at the performance metrics.
Please read the section 'Running tests: performance difference'. Note the significant time difference for both a small project and a larger one.
A 3-5x speed improvement isn't just a quality-of-life upgrade. It fundamentally changes what's practical in a test suite.
Advanced Testing: Fuzzing and Invariant Testing
This speed makes advanced, automated testing techniques a core feature of Foundry.
- Fuzz Testing: Instead of manually writing tests for edge cases (e.g.,
amount = 0,amount = type(uint256).max), you can define a property that should always be true and let Foundry hammer the function with thousands of random inputs to try and break it. - Invariant Testing: You define a state that should never change (an "invariant," e.g.,
totalDeposits - totalWithdrawals == contract.balance). Foundry then intelligently calls any and all functions on your contract in random sequences to see if it can break this invariant. This is incredibly powerful for finding unexpected state corruptions.
These features are often cited as Foundry's killer app, especially for security-focused development.
Solidity Development with Foundry: Cast, Anvil, Chisel, and Forge
This final video clip demonstrates the power of fuzzing and invariant testing in Foundry.
Watch from 42:50 to 49:32. Focus on understanding: Fuzzing: How simply adding an argument to a test function transforms it into a fuzzer. Invariant Testing: The concept of defining a property and letting Foundry try to break it by calling various functions.
While fuzzing is possible in Hardhat via plugins, it's not a built-in, high-performance feature as it is in Foundry.
4. Summary and The Hybrid Approach
Let's consolidate these differences. The Chainstack article provides a fantastic summary table.
This table is an excellent reference. It highlights that Hardhat excels in its familiar JS environment and deployment scripting, while Foundry shines with its performance, integrated tooling (cast), and native Solidity testing.
Because they excel in different areas, you don't have to choose just one. Many top teams adopt a hybrid approach:
- Foundry is used for unit testing, fuzzing, and invariant testing of the core contract logic due to its speed and security focus.
- Hardhat is used for complex deployment scripting and for integration tests that involve multiple contracts and simulate front-end interactions.
Conclusion
You've now taken a deep dive into the philosophies and workflows that separate Hardhat and Foundry.
Key Takeaways:
- Philosophical Divide: Hardhat embraces the JS/TS ecosystem, offering familiarity and a vast plugin library. Foundry champions a Solidity-native approach for performance and reduced context-switching.
- Testing Workflow: Hardhat uses a familiar
describe/itstructure with fluent Chai assertions. Foundry uses Solidity contracts for tests, relying onassertfunctions and a unique system of "cheat codes" (vm) to manipulate state. - Key Differentiators: Foundry's main advantages are its raw performance and the powerful, built-in fuzz and invariant testing capabilities this enables. Hardhat's strengths lie in its mature plugin ecosystem and flexible TypeScript-based scripting for deployments and complex integrations.
- Practical Choice: The "best" tool depends on the task. A hybrid approach, using Foundry for rigorous unit testing and Hardhat for deployment and integration, is a powerful and increasingly common strategy.
Next Steps:
Whether you're using Hardhat or Foundry, your tests run against a simulated blockchain environment. In the next lesson, we will explore how to set up and use these local blockchain nodes—Hardhat Network and Foundry's Anvil—and configure them for advanced use cases like mainnet forking.