Integration Testing Against Remote Neo4j

· 3 min read

Writing integration tests for your code that runs against Neo4j is simple enough when using the native API, but there’s not a great deal of help out there if you’re working in client-server mode. Making assertions about the shape of the graph can also be difficult, particularly if use cases involve more than a few nodes and relationships.

In all but the most simple of scenarios, it can be hard to see why the graph in the test database isn’t as expected, as the feedback is often as black-and-white as pass/fail so you can be looking for a proverbial needle in a haystack when trying to pinpoint the problem.

GraphUnit

Fortunately, GraphAware have developed a neat little utility called GraphUnit that does all the hard work for you. To take advantage of it, you’ll need to make sure that the following Java dependencies are on your classpath. If you’re unlucky enough to be using Maven, you can just copy the following XML block into your POM file.

	<dependency>
		<groupId>org.neo4j</groupId>
		<artifactId>neo4j</artifactId>
		<version>2.2.4</version>
	</dependency>
	<dependency>
		<groupId>org.neo4j</groupId>
		<artifactId>neo4j-kernel</artifactId>
		<version>2.2.4</version>
		<type>test-jar</type>
		<scope>test</scope>
	</dependency>
	<dependency>
		<groupId>com.graphaware.neo4j</groupId>
		<artifactId>tests</artifactId>
		<version>2.2.4.34</version>
		<scope>test</scope>
	</dependency>
	<dependency>
		<groupId>junit</groupId>
		<artifactId>junit</artifactId>
		<version>4.12</version>
		<scope>test</scope>
	</dependency>

Server Mode

You can learn all about how to use GraphUnit in embedded mode by reading this blog post but what about client-server mode? If you’ve got a use case where you’re testing against the transactional Cypher endpoint or one of the REST APIs then you’ll need to consider this. If you have unmanaged extensions then it can also be useful for verifying that these work correctly in their natural environment.

JUnit Rules

Since JUnit 4.9, Rules have been available for instrumenting your test cases. These offer a much more flexible approach than custom runners and abstract base classes and bootstrapping Neo4j is a great use for them.

	public class Neo4jIntegrationTestRule implements TestRule {

		private final WrappingNeoServerBootstrapper bootstrapper;
		private final GraphDatabaseService database;

		public Neo4jIntegrationTestRule() {
			this.database = new TestGraphDatabaseFactory().newImpermanentDatabase();

			ServerConfigurator configurator = new ServerConfigurator((GraphDatabaseAPI) this.database);
			configurator.configuration().addProperty(Configurator.WEBSERVER_PORT_PROPERTY_KEY, 7890); // pick any port you like
			this.bootstrapper = new WrappingNeoServerBootstrapper((GraphDatabaseAPI) this.database, configurator);
		}

		@Override
		public Statement apply(final Statement baseStatement, Description description) {
			this.bootstrapper.start();

			return new Statement() {
				@Override
				public void evaluate() throws Throwable {
					try {
						if (database.isAvailable(1000)) {
							baseStatement.evaluate();
						} else {
							Assert.fail("Database was shut down or didn't become available within 1s");
						}
					} finally {
						bootstrapper.stop();
					}
				}
			};
		}

		public GraphDatabaseService getGraphDatabaseService() {
			return this.database;
		}

	}

The Statement passed to the apply method represents the JUnit test to be run. Calling baseStatement.evaluate() actually runs the test, hence all the code around it is used to provide the infrastructure needed for test execution. The code above starts a Neo4j database running on local TCP port 7890, checks that the database is available and then runs the test, making sure to stop the bootstrapper again at the end of the test to shut the database down.

You can include it in your test case thus:

	public class ArbitraryIntegrationTest {

		@Rule
		public final Neo4jIntegrationTestRule neo4jRule = new Neo4jIntegrationTestRule();

		@Test
		public void arbitraryTestMethod() {

			// some code that's supposed to create a person node in the database running on http://localhost:7890

			GraphUnit.assertSameGraph("CREATE (:Person {name:'Sheila'})", neo4jRule.getGraphDatabaseService());
		}

    }

The @Rule annotation needs to be applied to the field for JUnit to use it. Note that the field must be public because of the way in which JUnit reads it when setting up your test harness. The rule’s apply(...) method gets called for each test method when used in this way.

There’s no need for duplicating set-up or tear-down methods in all of your test cases if you use this approach. Just including the rule as shown above will handle everything and expose the underlying graph database service for use with the GraphUnit framework as well.

You can also annotate the rule field with @ClassRule and declare it public static if you want its apply(...) method to be called just once for the whole test class. This is often a good idea because it starts and stops the database once for the entire test run and can save a lot of time if you have more than a few test methods. Of course, by not recreating the database each time for every test you run the risk of having data lying around from previous test methods, so it’s often a good idea to declare an @Before method in your test case to delete the contents of your database.

Adam George