Aion4j Tips —Unit Test your Avm Java Smart Contract with Spock Framework
In this post, I am going to explain how to use Spock Framework to unit test Java smart contract. Spock is a Groovy based testing framework and an alternative to the traditional JUnit framework. By making use of Groovy, Spock introduces new and expressive ways of testing Java applications.
In this post, we will be covering a basic Spock test and a test using Spock’s “Data Driven Testing” feature. To know more about Spock Framework, you can check Spock’s documentation.
Another goal of this post is to showcase how easily any existing JVM frameworks or tools can be used in Smart Contract development.
If you would like to know how to write and run JUnit test for your Java smart contract, you can check this blog written by Shidokth.
In short, Avm provides a JUnit rule called “AvmRule” which can be used to test contracts on an embedded Avm. We are going to use this class in this post.
The source code for this sample can be found over on GitHub.
a. Create a Java Contract Project
To create a Java smart contact project with IntelliJ IDE and BloxBean’s Aion4j Plugin, you can follow the steps on this Aion doc page.
In this post, we will be writing a very simple contract to demonstrate how to setup Spock unit test. We are going to create a smart contract which returns the sum of two numbers. I have intentionally kept it very simple, as the goal is to show case the usage of Spock framework for contract testing, not how to write Java contract.
b. Remove default generated smart contract files
Once you have created a new Java smart contract project in IntelliJ, you can remove the default generated contract “HelloAvm.java” (under src/main/java folder) and the corresponding test class, “HelloAvmRuleTest.java” (under src/test/java folder).
c. pom.xml change
- Open pom.xml file and add the following dependencies for Spock framework.
<dependency>
<groupId>org.spockframework</groupId>
<artifactId>spock-core</artifactId>
<version>1.3-groovy-2.4</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-all</artifactId>
<version>2.4.17</version>
<scope>test</scope>
</dependency>
2. We need to include the gmavenplus plugin in order to be able to compile and run our Spock groovy test scripts. Add the following plugin in plugins section.
<build>
<plugins>
...
...
<plugin>
<groupId>org.codehaus.gmavenplus</groupId>
<artifactId>gmavenplus-plugin</artifactId>
<version>1.5</version>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>testCompile</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
d. Create contract Java file
Create a new Java class for our contract under source package. Let’s call it “SumContract.java”.
Add the following content to the contract source file.
import org.aion.avm.tooling.abi.Callable;
public class SumContract {
@Callable
public static int sum(int i, int j) {
return i + j;
}
}
Note: Don’t replace the package section in the original file.
This contract has only one method “sum” which returns the sum of two numbers.
The @Callable annotation needs to be there for all public contract methods.
e. Contract main class in pom.xml
Update pom.xml to include SumContract class name as contract.main.class. To do that, edit <contract.main.class> element to include fully qualified SumContract class name.
Example:
<properties>
...<contract.main.class>org.aion4j.avm.sample.SumContract</contract.main.class></properties>
e. Create test class
We will be using Groovy to write our Spock test script.
- Create a folder called “groovy” under src/test folder.
- Create a new Groovy script, “SumContractTest.groovy” under src/test/groovy folder.
Note: Please make sure that the test groovy class name ends with “Test”, so that the Spock test script will be picked up during test run by maven sunfire plugin.
- “SumContractTest” groovy class should extend spock.lang.Specification. Each Spock class must extend this in order to make the framework available to it.
class SumContractTest extends Specification {}
- Let’s add AvmRule as a ClassRule in our test class. Define a setup() which will be running before every test method.
@ClassRule
@Shared
private AvmRule avmRule = new AvmRule(true);
private Address from;
private Address dappAddr;
def setup() {
from = avmRule.getPreminedAccount();
byte[] dapp = avmRule.getDappBytes(org.aion4j.avm.sample.SumContract, null);
dappAddr = avmRule.deploy(from, BigInteger.ZERO, dapp).getDappAddress();
}
Spock understands @org.junit.ClassRule
annotations on @Shared
fields. In setup(), we are deploying our SumContract class to an embedded Avm. This method gets called before every test method run.
- Our first test method checks the sum of two numbers, 4 and 5.
def "4 plus 5 should be equal to 9"() { when:
byte[] txData = ABIUtil.encodeMethodArguments("sum", 5, 4);
AvmRule.ResultWrapper result = avmRule.call(from, dappAddr, BigInteger.ZERO, txData);
int sum = Integer.parseInt(String.valueOf(result.getDecodedReturnData()));
then:
sum == 9;
}
In the above test method, there are two blocks with labels “when” and “then”.
Inside “when” block, we are
- Preparing method call transaction data
- Calling contract method through AvmRule.call()
- Getting the decoded result
Inside “then” block, the actual result is compared with the expected result.
According to Spock’s documentation,
Spock has built-in support for implementing each of the conceptual phases of a feature method. To this end, feature methods are structured into so-called blocks. Blocks start with a label, and extend to the beginning of the next block, or the end of the method. There are six kinds of blocks:
setup
,when
,then
,expect
,cleanup
, andwhere
blocks. Any statements between the beginning of the method and the first explicit block belong to an implicitsetup
block.In Spock, what we refer to as a feature is somewhat synonymous to what we see as a test in JUnit.
- Let’s write out second feature method. The second test shows one of the powerful feature of Spock called “Data Driven Testing”.
def "a plus b should equal to expected" (int a, int b, int expected) { given:
byte[] txData = ABIUtil.encodeMethodArguments("sum", a, b);
AvmRule.ResultWrapper result = avmRule.call(from, dappAddr, BigInteger.ZERO, txData);
int sum = Integer.parseInt(String.valueOf(result.getDecodedReturnData()));
expect:
sum == expected
where:
a | b | expected
4 | 6 | 10
9 | 2 | 11
23| 21| 44
}
Essentially, data driven testing is when we test the same behavior multiple times with different parameters and assertions. A example of this would be testing the sum operation. Depending on the various permutations of operands, the result will be different.
This test method takes three parameters a (first operand), b (second operand) and expected (expected sum value).
In “given” block, the transaction data is prepared and contract method is called through AvmRule.call().
In “expect” block, the returned result is compared with the expected value.
In “where” block, we are providing a datatable for our test with different operand pairs and corresponding expected results.
As we can see, we just have a straightforward and expressive Data table containing all our parameters. The test is expressive, with a human-readable name, and pure expect and where block to break up the logical sections.
This is one of the advantage of Spock when compared to JUnit because the way Spock implements parameterized testing.
Spock also has it’s own mocking framework. So in case you want to use mock/stubs inside your tests, you don’t need to use a separate mocking library.
Run Tests
- Build your project and run tests
You can now run “mvn clean install” to build and run your tests. You can also directly run these tests from IntelliJ’s editor.
In this post, we have just scratched the surface. For more info about Spock framework, you can check Spock’s documentation page.
Resources
- Sample Code : https://github.com/satran004/avm-samples/tree/master/SumContract-spock-sample
- Spoke Framework : http://spockframework.org/
- AVM GitHub : https://github.com/aionnetwork/avm
- BloxBean : https://www.bloxbean.com/
- Avm Smart Contract with IntelliJ IDE: https://docs.aion.network/docs/intellij-plugin