Blockchain Cryptocurrency Dev Tools Ethereum Smart Contracts Web3
Ranjithkumar  

Smart Contract Testing: Javascript vs Solidity

Smart contracts, the self-executing code on blockchains, require rigorous testing to ensure their security and functionality. Two primary approaches emerge: testing in Javascript and testing directly in Solidity. This blog post delves into the pros and cons of each method, along with popular frameworks like Hardhat and Foundry.

Javascript Testing:

  • Pros:
    • Familiarity: Developers with Javascript experience can easily transition to writing tests.
    • Rich Ecosystem: Javascript boasts a mature testing ecosystem with established frameworks like Mocha and Chai.
    • Higher-Level Abstractions: Javascript frameworks facilitate mocking and manipulating blockchain environments, allowing for broader test coverage.
  • Cons:
    • Indirectness: Tests interact with the deployed smart contract through emulated environments, potentially missing edge cases specific to the blockchain’s execution.
    • Dependency on Frameworks: Javascript tests rely on frameworks for blockchain interaction, adding an extra layer of complexity.

Solidity Testing:

  • Pros:
    • Directness: Tests directly interact with the deployed contract on a real blockchain, capturing the exact execution environment.
    • Language Consistency: Writing tests in the same language as the contract promotes code readability and reduces context switching.
  • Cons:
    • Learning Curve: Developers unfamiliar with Solidity need to acquire new skills for writing tests.
    • Limited Ecosystem: The Solidity testing ecosystem is still evolving, with fewer established frameworks compared to Javascript.

Hardhat vs. Foundry:

Both Hardhat and Foundry are popular frameworks for smart contract development, each offering functionalities for testing:

  • Javascript with Hardhat: Provides a comprehensive environment for development, testing, and deployment, including a built-in testing framework with assertions and mocks. Ideal for developers familiar with Javascript and for situations where rapid test development and a rich ecosystem are crucial.
  • Solidity with Foundry: Emphasizes developer experience with a focus on ease of use and rapid testing. It utilizes Forge, a custom language based on Solidity, for writing tests and interacting with the blockchain. Preferred for projects requiring the highest level of test accuracy and for teams comfortable with Solidity development.

Ultimately, a hybrid approach combining both Javascript and Solidity testing might be optimal for certain scenarios, leveraging the strengths of each method. Continuously evaluate your project’s needs and adapt your testing strategy accordingly.

Javascript with Hardhat

Project Setup:

  1. Initialize a Node.js project: npm init -y
  2. Install Hardhat: npm install --save-dev hardhat
  3. Create a Hardhat project: npx hardhat init (Choose an empty hardhat.config.js)

Sample Smart Contract (Greeter.sol):

pragma solidity ^0.8.9;

contract Greeter {
    string private greeting;

    constructor(string memory _greeting) {
        greeting = _greeting;
    }

    function greet() public view returns (string memory) {
        return greeting;
    }

    function setGreeting(string memory _newGreeting) public {
        greeting = _newGreeting;
    }
}

Test File (test/greeter.js):

const { expect } = require("chai");
const { ethers } = require("hardhat");

describe("Greeter", function () {
  it("Should deploy with a greeting", async function () {
    const Greeter = await ethers.getContractFactory("Greeter");
    const greeter = await Greeter.deploy("Hello, world!");
    await greeter.deployed();

    expect(await greeter.greet()).to.equal("Hello, world!");
  });

  it("Should be able to change the greeting", async function () {
    const Greeter = await ethers.getContractFactory("Greeter");
    const greeter = await Greeter.deploy("Hola");
    await greeter.deployed();

    await greeter.setGreeting("Bonjour");
    expect(await greeter.greet()).to.equal("Bonjour");
  });
});

Explanation:

  1. Imports:
    • chai: Testing assertion library.
    • hardhat: Provides the Hardhat Runtime Environment for interacting with the Ethereum network.
  2. describe block:
    • Defines a test suite for the Greeter contract.
  3. it blocks:
    • Individual test cases.
    • The first test checks if the contract deploys with the correct initial greeting.
    • The second test verifies the ability to update the greeting.
  4. ethers.getContractFactory:
    • Compiles the Greeter contract and creates a factory to deploy instances.
  5. greeter.deployed():
    • Deploys the contract and waits for its deployment to complete.
  6. expect(...).to.equal(...):
    • Chai assertions to verify if contract state matches expectations.

Running Tests:

Use the following command in your project directory:

npx hardhat test

Solidity with Foundry

Project Setup

  1. Install Foundry: Follow the instructions at https://getfoundry.sh/
  2. Initialize Project: Run forge init in an empty directory.

Sample Smart Contract (Counter.sol)

Solidity

pragma solidity ^0.8.9;

contract Counter {
    uint256 public count;

    function increment() public {
        count++;
    }

    function decrement() public {
        require(count > 0, "Cannot decrement below zero");
        count--;
    }
}

Test File (test/Counter.t.sol)

pragma solidity ^0.8.9;

import "forge-std/Test.sol";

contract CounterTest is Test {
    Counter public counter;

    function setUp() public {
        counter = new Counter();
    }

    function testIncrement() public {
        counter.increment();
        assertEq(counter.count(), 1);
    }

    function testDecrement() public {
        counter.decrement(); 
        assertEq(counter.count(), 0); 
    }

    function testDecrementFail() public {
        vm.expectRevert(bytes("Cannot decrement below zero")); 
        counter.decrement();
    }
}

Explanation

  1. Import:
    • forge-std/Test.sol: Foundry’s standard testing library, providing assertion functions, cheatcodes (for simulating conditions), etc.
  2. CounterTest contract:
    • Inherits from the Test contract. Test contracts must inherit from Test.
  3. setUp() function:
    • Deploys a fresh instance of the Counter contract before each test case.
  4. test... functions:
    • Individual test cases.
    • testIncrement and testDecrement test the basic functionality of the counter.
    • testDecrementFail demonstrates how to test for expected reverts using vm.expectRevert.
  5. assertEq():
    • A built-in assertion function to verify if the expected and actual values match.
  6. vm:
    • A global object in Foundry tests, providing cheatcodes to interact with the blockchain environment (e.g., vm.expectRevert for checking reverts).

Running the Tests

In your project directory, use the following command:

forge test

Conclusion

Both Javascript and Solidity offer valuable approaches for testing smart contracts, each with distinct advantages and disadvantages. Javascript testing shines with familiarity, a rich ecosystem of tools, and higher-level abstractions. However, it can lack directness and introduce dependencies on frameworks. In contrast, Solidity testing boasts direct interaction with the deployed contract, ensuring accurate test execution, and maintaining code consistency. However, it comes with a steeper learning curve and a less mature ecosystem.

Ultimately, the choice depends on your project’s specific needs and your team’s expertise. For rapid prototyping and projects with a strong Javascript focus, Javascript testing might be the ideal starting point. However, for projects demanding the highest level of test accuracy and dealing primarily with Solidity development, Solidity testing is a compelling choice. In some scenarios, a hybrid approach incorporating elements of both methods could offer the best of both worlds.

Leave A Comment