I just lately tried testing solidity contracts with rspec and needed to share outcomes since I have not actually discovered anybody documenting the method.
Why?
Initially, I simply needed to check whether or not it is attainable as a result of I like ruby and attempt to stick it anyplace I can. End result turned out to be fairly good, although. I have not discovered any massive variations with truffle suite.
With out additional ado. Naive implementation
Conditions
- Set up ganache with
npm -g ganache-cli
- Set up solidity compiler. When you’re on mac you’ll be able to simply use
brew set up solidity
. If not here’s a link to documentation
Our sensible contract
For this tutorial. We’ll use a easy hey world contract. situated in contracts/greeter.sol
pragma solidity >= 0.8.11;
contract Greeter {
string personal _greeting = "Hey, World!";
deal with public proprietor;
constructor() {
proprietor = msg.sender;
}
operate greet() exterior view returns(string reminiscence) {
return _greeting;
}
operate setSuperGreeting(string calldata greeting) exterior {
require(msg.sender == proprietor, "Solely proprietor");
_greeting = greeting;
}
}
As you’ll be able to see it permits to learn from block and write from a blockchain. Easy however ok for our exams.
Our take a look at
Spec setup
First, we might want to begin ganache with simply
> ganache-cli
Then following etherium.rb documentation all we have to do is
- Create native consumer
consumer = Ethereum::HttpClient.new('http://localhost:8545')
- Create occasion of our contract by offering path to a file and occasion of a consumer
Ethereum::Contract.create(file: "greeter.sol", consumer: consumer)
- Deploy contract
contract.deploy_and_wait
Our preliminary setup will seem like this
require 'spec_helper.rb'
RSpec.describe 'Greeter' do
let(:consumer) { Ethereum::HttpClient.new('http://localhost:8545') }
let(:contract) { Ethereum::Contract.create(file: "contracts/greeter.sol", consumer: consumer) }
earlier than { contract.deploy_and_wait }
Asserts
First, let’s take a look at our greet methodology. To learn from a blockchain with etherium.rb we’ll want to make use of name
like so contract.name.method_name
. Our take a look at case will seem like this
it 'reads greeting' do
count on(contract.name.greet).to eq("Hey, World!")
finish
To vary state of the blockchain we have to use transaction like so contract.transact_and_wait.method_name
.
Right here is an instance of our subsequent assertion
it 'adjustments message' do
contract.transact_and_wait.set_super_greeting("Yo")
count on(contract.name.greet).to eq("Yo")
finish
Our complete spec will seem like this
require 'spec_helper'
RSpec.describe 'Greeter' do
let(:consumer) { Ethereum::HttpClient.new('http://localhost:8545') }
let(:contract) { Ethereum::Contract.create(file: "contracts/greeter.sol", consumer: consumer) }
earlier than { contract.deploy_and_wait }
it 'units greeting' do
count on(contract.name.greet).to eq("Hey, World!")
finish
it 'adjustments message' do
contract.transact_and_wait.set_super_greeting("Yo")
count on(contract.name.greet).to eq("Yo")
finish
finish
Working
> bundle exec rspec
2 examples, 0 failures
As you’ll be able to see it is tremendous straightforward solely a pair drawbacks.
- Want to begin ganache by hand
- No easy accessibility to accounts
Subsequent Iteration
To beat the above drawbacks I’ve created a simple gem. It’s going to begin ganache earlier than the exams and supply entry 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 it is advisable do now to make code work is add kind: :smart_contract
. RSpec::Eth
gives just a few strategies that’ll make our specs even easy. We now can take away consumer
and contract
. Because the gem will guess contract location by spec location.
Up to date model
require 'spec_helper'
RSpec.describe 'Greeter', kind: :smart_contract do
earlier than { contract.deploy_and_wait }
it 'units greeting' do
count on(contract.name.greet).to eq("Hey, World!")
finish
it 'adjustments message' do
contract.transact_and_wait.set_super_greeting("Yo")
count on(contract.name.greet).to eq("Yo")
finish
finish
Now cease ganache if it is nonetheless working and check out working specs once more
> bundle exec rspec
2 examples, 0 failures
Conclusion
It is nonetheless a piece in progress and the method could be improved by including additional configuration choices and contract-specific matchers. Nonetheless, should you like me love utilizing ruby each time you’ll be able to, I consider truffle is not the one toolset you should use for testing sensible contracts