Running a Pantheon Node in Java Integration Tests

The first problem you are likely to meet when attempting to write integration tests for your Java Ethereum application is that you need a running node to connect to for sending transactions. One option to overcome this is to run a node yourself manually in the background, but this becomes hard to manage if you want to run your tests in a CI pipeline, and forcing all contributors to you codebase to run a node manually is not ideal. Luckily there's a better way!

Prerequisites

Running a Node with Testcontainers

Testcontainers is a useful library that allows you to fire up a Docker container programmatically within your test code, and there are several Ethereum clients that have ready-made Docker containers uploaded to Dockerhub, which makes this task easier.

In this guide, I describe how to start and shutdown a Pantheon node during your integration tests, so you don't have to start a node manually or within your CI pipeline

Including the Testcontainers library

We get the Testcontainers library dependency via maven central, so to include the library, add the following dependency to your pom.xml (or equivalent in Gradle):

<dependency>
 <groupId>org.testcontainers</groupId>
 <artifactId>testcontainers</artifactId>
 <version>1.12.0</version>
 <scope>test</scope>
</dependency>

Starting Pantheon

It's better and more performant to start Pantheon once, before all tests execute, rather than before every test. To achieve this behaviour, we instantiate a static GenericContainer annotated with a @ClassRule JUnit annotation.

Create a new class in the project test folder, and add the code below to it:

ClassRule

@ClassRule
public static final GenericContainer pantheonContainer =
 new GenericContainer("pegasyseng/pantheon:1.1.3")
 .withExposedPorts(8545, 8546)
 .withCommand(
 "--miner-enabled",
 "--miner-coinbase=0xfe3b557e8fb62b89f4916b721be55ceb828dbd73",
 "--rpc-http-enabled",
 "--rpc-ws-enabled",
 "--network=dev")
 .waitingFor(Wait.forHttp("/liveness").forStatusCode(200).forPort(8545));

Before any tests run, a GenericContainer is instantiated, with a docker image name as an argument. We're using the 1.1.3 version of Pantheon in this instance, and we expose the standard default ports for HTTP and websocket RPC with the withExposedPorts(..) method.

We set some runtime command arguments which configure the node in a way that is suitable for testing the following:

For a full list of all available Pantheon commands, read the official documentation.

Waiting for Pantheon to Start

We must wait for Pantheon to start before running our tests fully. Pantheon comes autoconfigured with a liveness endpoint, so testcontainers automatically polls the /liveness endpoint on port 8545 until it returns a 200 response. We can then be confident that Pantheon is running.

Connecting to the Pantheon container using Web3j

Pantheon should now be up and running on localhost. You can now connect to the Pantheon node within your test classes, and perform Ethereum operations such as sending transactions by using Web3j:

private Web3j web3j;

private Credentials credentials = 
    Credentials.create("0x8f2a55949038a9610f50fb23b5883af3b4ecb3c3bb792cbcefbd1542c692be63");  

@Before
public void initWeb3() {
  final Integer port = pantheonContainer.getMappedPort(8545);
  web3j = Web3j.build(new HttpService(
      "http://localhost:" + port), 500, Async.defaultExecutorService());
}

Mapped Port

We exposed the default JSON RPC port, 8545 when creating the container, but did not map it to the same port on localhost. A random available port is automatically selected instead, which is beneficial because it removes the chance of the port not being open on your test machine (which could happen if you are running the tests in parallel for example).

To get the mapped port number, call the getMappedPort(..) method on the container. We can use these ports when constructing the Web3j connection URL.

Polling Interval

By default, Web3j polls the connected Ethereum client every 10 seconds for operations such as getting the latest mined blocks and checking if events have been emitted. Our Pantheon test network generally creates blocks much faster than every 10 seconds, so reducing the poll interval in Web3j should increase the speed of the tests. We can pass the poll interval to the Web3j.build static method, and above, we configure the interval to 500ms.

Test Credentials

Sending transactions in our private dev network still requires gas, so we must have access to an account with a positive balance, and the development network has some accounts pre-loaded with more test Ether than you could ever need! The private keys of these accounts are documented here, which makes it easy to generate a Credentials object for use in your tests.

Stopping Pantheon / Web3j

As we're using a @ClassRule annotation, stopping the Pantheon container is handled automatically at the end of the test class execution. Its a good idea to shutdown the web3j instance after each test though:

@After
public void shutdownWeb3j() {
 web3j.shutdown();
}

Summary

Using the Testcontainers library to start a Pantheon node is a convenient way to ensure that an Ethereum node is accessible to your tests. This makes running the tests in your continuous integration pipeline less arduous, and also means that other third party contributors can run your tests on their local machine more easily.

You can find an example test class demonstrating the code described in this tutorial on GitHub, from the java-web3j-pantheon-testing project.