Testing with Ganache CLI

Build Unit Tests with Ganache CLI

You can use Ganache CLI for testing. You can install it using NPM (Node Package Manager). But I prefer to use Docker. Install Docker first. You can run Ganache CLI using Docker this way:


$ docker run -p 8545:8545 trufflesuite/ganache-cli:latest

The process of Ganache CLI will take over the terminal. So to work on Mamba project, we need to open another terminal. Create a directory and initialize the directory. We need to make sure we have installed Mamba.


(mamba-venv) $ mkdir donate
(mamba-venv) $ cd donate
(mamba-venv) $ mamba init

Let's create a simple contract that demonstrates the sending ethers capability. Create a new file, contracts/Donate.vy.


donatur: public(address)
donatee: public(address)

@external
def __init__():
    self.donatee = msg.sender

@payable
@nonreentrant("donate")
@external
def donate():
    assert msg.sender != self.donatee
    self.donatur = msg.sender

@payable
@external
@nonreentrant("withdraw")
def withdraw_donation():
    assert self.donatee == msg.sender
    send(msg.sender, self.balance)

This is just a simple contract on which we can donate to other people via smart contract. The donate method will send ethers to the smart contract. The withdraw_donation will withdraw ethers from the smart contract to the donatee. The @payable decorator makes the method be able to receive and send ethers. The send method means the smart contract sends ethers to the first parameter account. The second parameter is the number of ethers you want to send. self.balance is the amount of ethers the smart contract has. Compile this smart contract code.


(mamba-venv) $ mamba compile

Then let's create a test for this smart contract in test/test_donate.py.


from black_mamba.testlib import contract, w3, TestContract
from web3 import Web3


class TestDonate(TestContract):

    def test_initial_contract(self, w3):
        donate_contract = contract("Donate", [])
        assert donate_contract.functions.donatee().call() == w3.eth.accounts[0]

    def test_donate(self, w3):
        donate_contract = contract("Donate", [])
        accounts = w3.eth.accounts
        initial_balance = w3.eth.getBalance(accounts[1])
        tx = { "from": accounts[1], "value": Web3.toWei("10", "ether"),
               "gas": 200000, "gasPrice": Web3.toWei("200", "gwei") }
        donate_contract.functions.donate().transact(tx)
        after_balance = w3.eth.getBalance(accounts[1])
        number = initial_balance - Web3.toWei("10", "ether") - after_balance
        assert 0 < number < Web3.toWei("0.01", "ether")
        contract_balance = w3.eth.getBalance(donate_contract.address)
        assert contract_balance == Web3.toWei("10", "ether")

    def test_withdraw_donation(self, w3):
        donate_contract = contract("Donate", [])
        accounts = w3.eth.accounts
        tx = { "from": accounts[1], "value": Web3.toWei("10", "ether"),
               "gas": 200000, "gasPrice": Web3.toWei("200", "gwei") }
        donate_contract.functions.donate().transact(tx)
        initial_balance = w3.eth.getBalance(accounts[0])
        tx = { "from": accounts[0], "gas": 200000, "gasPrice": Web3.toWei("200", "gwei") }
        donate_contract.functions.withdraw_donation().transact(tx)
        after_balance = w3.eth.getBalance(accounts[0])
        number = initial_balance + Web3.toWei("10", "ether") - after_balance
        assert 0 < number < Web3.toWei("0.01", "ether")
        contract_balance = w3.eth.getBalance(donate_contract.address)
        assert contract_balance == Web3.toWei("0", "ether")

We need to use the class test in order to use Ganache CLI. Make sure your test class inherits TestContract. Mamba provides w3 fixture so you can use it in your method. It's a web3 object. Make sure you put the fixture not in the first parameter because the first parameter is the instance of the class (self). To get the balance we can use w3.eth.getBalance that accepts the address as the parameter. We have test accounts that we can access from w3.eth.accounts. The first account (w3.eth.accounts[0]) is the one which deployed the smart contract. To test the balance of the account after accessing the method of the smart contract which sends and receives ethers, we need to account for the gas as well. Say, our account has 100 ethers and we sends 10 ethers to the smart contract. The final balance of our account is not 90 ethers but 89.9983 ethers because we have to pay the gas. That's why we have to resort to this kind of kung-fu. We assume we don't pay more than 0.01 ethers for gas.


        number = initial_balance - Web3.toWei("10", "ether") - after_balance
        assert 0 < number < Web3.toWei("0.01", "ether")

The Ganache CLI runs on http://127.0.0.0:8545 as we can see when we started it using Docker. You need to make sure the settings.py has the correct configuration.


ganache_cli = {
    "development": {
        "address": "http://127.0.0.1:8545"
    }
}

We can run the test as usual with Pytest.


(mamba-venv) $ py.test test/test_donate.py

The test should run perfectly.