Search code examples
javatestingneo4jneo4j-apocapoc

neo4j procedure testing with apoc does not compile


I've a project where I can successfully test my user procedure written using the neo4j traversal framework based on example testing projects.

The relevant part of the pom file is

...
  <neo4j.version>5.12.0</neo4j.version>
  <neo4j-java-driver.version>5.3.0</neo4j-java-driver.version>
</properties>
  <dependencies>
    <dependency>
      <groupId>org.neo4j</groupId>
      <artifactId>neo4j</artifactId>
      <version>${neo4j.version}</version>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>org.neo4j.test</groupId>
      <artifactId>neo4j-harness</artifactId>
      <version>${neo4j.version}</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.neo4j.driver</groupId>
      <artifactId>neo4j-java-driver</artifactId>
      <version>${neo4j-java-driver.version}</version>
      <scope>test</scope>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.neo4j.procedure/apoc -->
    <dependency>
      <groupId>org.neo4j.procedure</groupId>
      <artifactId>apoc</artifactId>
      <version>4.4.0.1</version>
      <scope>test</scope>
    </dependency>
...

I've set up the embeddedDatabaseServer like this

@BeforeEach
void initializeNeo4j() throws IOException {
    
    var sw = new StringWriter();
    try (var in = new BufferedReader(new InputStreamReader(getClass().getResourceAsStream("/getNode/tvlike_test_data.cypher")))) {
        in.transferTo(sw);
        sw.close();
    }
    //split into each statement so can be run one by one
    List<String> statements = Arrays.asList(sw.toString().split(";\r\n"));

    var builder = Neo4jBuilders.newInProcessBuilder()
            .withDisabledServer()
            .withProcedure(GetNode.class);

    for (String statement : statements) {
        builder.withFixture(statement);
    }
    
    this.embeddedDatabaseServer = builder.build();
}

and the tests run successfully e.g.

@Test
void TryIt() {
            try (
                    var driver = GraphDatabase.driver(embeddedDatabaseServer.boltURI());
                    var session = driver.session()
    
            ) {
    
                var results = session.run("""
                                match(u:User{userId:'u1'})
                                where u.isEnabled = true
                                match(n:Trial{id: 'tr1'})
                                call cctc.getNode(n, u, { includeFoundPermissions : true }) 
                                    yield allNodePerms, allPropPerms
                                return allNodePerms, allPropPerms""")
                        .single();
                var resMap = results.asMap();
    
                //
                var allNodePerms = (List<Map<String, Object>>) resMap.get("allNodePerms");
                assertThat(allNodePerms.size()).isEqualTo(1);
            }
        }

I'd like to include apoc procs and functions in my testing, but I can't add the necessary infrastructure successfully. For example, running this

@Test
void TryIt2() {
    try (
            var driver = GraphDatabase.driver(embeddedDatabaseServer.boltURI());
            var session = driver.session()

    ) {

        var results = session.run("""
                        return apoc.text.code(103) AS output""")
                .single();

        assertThat(results.get("output")).isEqualTo("g");
    }}

gives the error

org.neo4j.driver.exceptions.ClientException: Unknown function 'apoc.text.code' (line 1, column 8 (offset: 7)) "return apoc.text.code(102) AS output"

as you'd expect.

The question is how do I add the apoc library into the embedded server in the set up step? I've tried many examples found in various samples but without success.

The pom file already includes the dependency for the apoc procedures. I've tried amending the @BeforeEach method in various ways, e.g.

but without success.

How can I amend this

        var builder = Neo4jBuilders.newInProcessBuilder()
                .withDisabledServer()
                .withProcedure(GetNode.class);

in my initializeNeo4j method to include apoc?


Solution

  • First of all, you should ensure that your neo4j dependencies have compatible versions. You are currently using wildly different versions:

    • Version 5.12.0 of neo4j
    • Version 5.3.0 of neo4j-java-driver
    • Version 4.4.0.1 of apoc.

    To keep all 3 dependencies consistent, the correct dependencies for the latter 2 artifacts are as follows (notice that the artifactId for core apoc is apoc-core in 5.x):

    <dependency>
        <groupId>org.neo4j.driver</groupId>
        <artifactId>neo4j-java-driver</artifactId>
        <version>${neo4j.version}</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.neo4j.procedure</groupId>
        <artifactId>apoc-core</artifactId>
        <version>${neo4j.version}</version>
        <scope>test</scope>
    </dependency>
    

    In general, to use an APOC core function (or procedure) in your embedded server, you need to find the APOC core class that implements the function (or procedure). To do that, you can:

    1. Select the Tag of the version you are using in the APOC core github repo.
    2. Search for the name of the function (or procedure). For example, for apoc.text.code, look for a match that looks like @UserFunction("apoc.text.code").
    3. Get the name of the APOC class (including the package) that implements what you are looking for. For example, apoc.text.Strings.
    4. In your class that calls newInProcessBuilder, add an import of that APOC class.
    5. Call the builder's withFunction (or withProcedure) method and pass it the APOC class. For example, withFunction(Strings.class).