In this tutorial we will write tests for a Celo smart contract.
Writing tests is an important part of smart contract development because smart contract vulnerabilities can have real world implications like losing user funds! The best way to try to mitigate vulnerabilities is to write extensive unit tests in order to test every part of our contract's functionality.
This is a beginner friendly tutorial. Read on to learn about testing!
For this tutorial you will need to install Truffle and Ganache. Truffle is a set of tools for smart contract development and Ganache is a tool for creating local blockchains.
In your console, run the following:
npm install -g truffle
npm install -g ganache-cli
Great! That's all we'll need for our smart contract testing.
Next, we're going to setup our project. Let's create a folder called
cdinto the folder:
mkdir celoTesting && cd celoTesting
Now let's initialize Truffle:
There should now be a handful of files and folders in your
celoTestingdirectory. Let's open
celoTestingwith your favorite text editor and continue.
Since this tutorial focuses on smart contract testing, we'll use a very simple Solidity contract in order to get our bearings.
Create a new smart contract in the
HelloWorld.soland write the following:
Great! This is just a simple smart contract which will either get the
namevariable by calling
getName()or update the
namevariable by calling
setName(). When the contract first runs,
namewill be "Celo".
Now that we have a smart contract, let's deploy it to make sure it works. We'll be deploying it on the Ganache CLI, which is a command line tool for creating local blockchains. Ganache will create a local Ethereum blockchain for us to test on. Since Celo is a fork of Ethereum, they use the same environment.
To spin up the local blockchain, in a new terminal window run:
ganache-cli --port 8545
We use a new terminal window since we'll want to keep Ganache running while we develop.
Next, replace the existing contents of
truffle.configwith the following:
This will simply use the Ganache environment whenever we deploy or test our contracts. Our Ganache environment is running on
localhost(127.0.0.1) and using port 8545.
We'll also want to create a
migrationsfile for our smart contract to deploy from. Create a file and call it
migrations/folder and write the following:
After all that, we can run
truffle migratein our terminal to deploy our contracts.
The results should look like this:
An important thing to note about testing in Truffle is that whenever we run the tests, Truffle will create a new contract deployment. This means we get a fresh start whenever we run our tests, and our contract gets reset.
Moving on to development, in the
test/folder create a new file called
The first line we'll write will get our smart contract instance:
const HelloWorld = artifacts.require("HelloWorld");
Next, let's setup our testing file:
Before we write our tests, let's review the goals of writing unit tests. A unit test should:
- Test one method
- Provide some specific arguments to that method
- Test that the result is as expected
Let's test that our contract returns "Celo" from
HelloWorldcontract. If you'll recall,
getName()returns whatever the string inside the
namevariable is. By default, we set name to be
Celowhen the contract is created.
In this test, we create an
HelloWorldcontract deployment, and call the
getName()function on it. The syntax for this is simply
instance.getName.call()for calling functions which read from the blockchain.
Next, our test
assertsthat the value returned from
getName()is equal to the string 'Celo'.
Let's run this! In a terminal window (with ganache running), type:
This will run all our tests. Our test passes!
Now that we have our first test which tests the
getName()function, let's write a test to check
For this second test, we use
setName()to change the name variable to "Figment Learn" and then call
getName()to check if the variable updated.
In this test, we again retrieve an instance of the
HelloWorldcontract, but this time we call the
setName()function on it like so:
await instance.setName('Figment Learn');
getName(), we don't use
.call(). Whenever we're calling functions that write to the blockchain, we won't need to use
Great! This works. The test does take a while longer than the first one because it calls two functions and waits for each of them to finish.
A big part of unit testing is testing the edge cases which could possibly break your code. An example could be using special characters like emojis in our
setName()function. Let's write a test to make sure that works:
And finally, let's run this last test:
Woohoo! All our tests pass.
Now we know how to write tests for our smart contracts. This skill will come in handy whenever you're working on a smart contract and need to verify that all of the functionality works. Although our tests were relatively simple, they should be enough to get you started on your testing journey.
After following this tutorial, you should have a basis for writing tests for smart contracts. Unit testing is a entire subsection of development which has lots of discussion around it. If you'd like to read more, here are some additional resources:
- Writing automated smart contract tests - A tutorial from OpenZeppelin
- Unit testing best practices - This isn't specific to Solidity, but writing great unit tests is a concept that extends to development across all programming.
- New to unit testing, how to write great tests? - A stack overflow question with some great answers on writing great tests.
- Testing deployed smart contracts - This tutorial may come in handy if you're trying to test a contract which has been deployed to the Celo mainnet.
This tutorial was created by Alex Reyes. Alex is a student (BS, Computer Science) and crypto enthusiast who's learning all about the world of web3 one day at a time and he's contributing to Web3 communities actively. He has previously completed internships at Facebook and Microsoft.