How to test solidity smart contracts with ruby, RSpec and Etherium.rb
TheSmartnik
Posted on March 30, 2022
I recently tried testing solidity contracts with rspec and wanted to share results since I haven't really found anyone documenting the process.
Why?
Initially, I just wanted to test whether it's possible because I like ruby and try to stick it anywhere I can. Result turned out to be pretty good, though. I haven't found any big differences with truffle suite.
Without further ado. Naive implementation
Prerequisites
- Install ganache with
npm -g ganache-cli
- Install solidity compiler. If you're on mac you can just use
brew install solidity
. If not here is a link to documentation
Our smart contract
For this tutorial. We'll use a simple hello world contract. located in contracts/greeter.sol
pragma solidity >= 0.8.11;
contract Greeter {
string private _greeting = "Hello, World!";
address public owner;
constructor() {
owner = msg.sender;
}
function greet() external view returns(string memory) {
return _greeting;
}
function setSuperGreeting(string calldata greeting) external {
require(msg.sender == owner, "Only owner");
_greeting = greeting;
}
}
As you can see it allows to read from block and write from a blockchain. Simple but good enough for our tests.
Our test
Spec setup
First, we'd need to start ganache with just
> ganache-cli
Then following etherium.rb documentation all we need to do is
- Create local client
client = Ethereum::HttpClient.new('http://localhost:8545')
- Create instance of our contract by providing path to a file and instance of a client
Ethereum::Contract.create(file: "greeter.sol", client: client)
- Deploy contract
contract.deploy_and_wait
Our initial setup will look like this
require 'spec_helper.rb'
RSpec.describe 'Greeter' do
let(:client) { Ethereum::HttpClient.new('http://localhost:8545') }
let(:contract) { Ethereum::Contract.create(file: "contracts/greeter.sol", client: client) }
before { contract.deploy_and_wait }
Asserts
First, let's test our greet method. To read from a blockchain with etherium.rb we'll need to use call
like so contract.call.method_name
. Our test case will look like this
it 'reads greeting' do
expect(contract.call.greet).to eq("Hello, World!")
end
To change state of the blockchain we need to use transaction like so contract.transact_and_wait.method_name
.
Here is an example of our next assertion
it 'changes message' do
contract.transact_and_wait.set_super_greeting("Yo")
expect(contract.call.greet).to eq("Yo")
end
Our whole spec will look like this
require 'spec_helper'
RSpec.describe 'Greeter' do
let(:client) { Ethereum::HttpClient.new('http://localhost:8545') }
let(:contract) { Ethereum::Contract.create(file: "contracts/greeter.sol", client: client) }
before { contract.deploy_and_wait }
it 'sets greeting' do
expect(contract.call.greet).to eq("Hello, World!")
end
it 'changes message' do
contract.transact_and_wait.set_super_greeting("Yo")
expect(contract.call.greet).to eq("Yo")
end
end
Running
> bundle exec rspec
2 examples, 0 failures
As you can see it's super easy only a couple drawbacks.
- Need to start ganache by hand
- No easy access to accounts
Next Iteration
To overcome the above drawbacks I've created a simple gem. It'll start ganache before the tests and provide access to accounts
Require gem
Let's add our gem to a Gemfile
gem 'rspec-eth'
and require it in spec_helper.rb
with
require 'rspec/eth'
Change spec
All you need to do now to make code work is add type: :smart_contract
. RSpec::Eth
provides a few methods that'll make our specs even simple. We now can remove client
and contract
. As the gem will guess contract location by spec location.
Updated version
require 'spec_helper'
RSpec.describe 'Greeter', type: :smart_contract do
before { contract.deploy_and_wait }
it 'sets greeting' do
expect(contract.call.greet).to eq("Hello, World!")
end
it 'changes message' do
contract.transact_and_wait.set_super_greeting("Yo")
expect(contract.call.greet).to eq("Yo")
end
end
Now stop ganache if it's still running and try running specs again
> bundle exec rspec
2 examples, 0 failures
Conclusion
It's still a work in progress and the process can be improved by adding extra configuration options and contract-specific matchers. However, if you like me love using ruby whenever you can, I believe truffle isn't the only toolset you can use for testing smart contracts
Posted on March 30, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.