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
Top comments (0)