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 , and where blocks. Any statements between the beginning of the method and the first explicit block belong to an implicit setup 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.