cancel
Showing results for 
Search instead for 
Did you mean: 

Head's Up! Site migration is underway. Phase 2: migrate recent content

How to mock org.neo4j.graphdb.Transaction for unit tests?

martijn
Node

All examples of unit/integration tests that I can find, all involve getting a Session or Transaction from a driver, and then executing a query. But for a plugin function I wrote, I don't just want to test the end result of the query, I want to test individual parts of the process. A unit test, instead of an integration test. Although obviously it's still going to need a transaction to some sort of database that contains data that can be read or manipulated (only read in my current case, but possibly written in another case I'm considering).

Does something like this exist? Passing org.neo4j.driver.Transaction clearly doesn't work. But how can I get a working org.neo4j.graphdb.Transaction to pass to my code? I can't find any example or documentation dealing with this issue, but it seems like a rather obvious thing to do.

1 ACCEPTED SOLUTION


@martijn wrote:

I also want to test that query.


Extract the query execution part and test it with Neo4j's test harness or TestContainers/Neo4j.

For the rest, I would indeed suggest refactoring the plugin so that test unit testing becomes easier ... OR test everything through slower integration/end-to-end tests.

View solution in original post

6 REPLIES 6

Could you please share the skeleton of your plugin class? That would help tailor the answer a bit more.
I'll assume for now you want to unit test a custom Cypher procedure or custom user-defined function?

If so, if you really want to unit test this, you can manually instantiate your procedure/function class, either use reflection or define some package-private test-only setters to set the mocked GraphDatabaseService  (or any other component you inject with @Context).

Once you've done that, you're pretty free to verify any interaction you like with the mocks.

Now the question is: is it worth it? If you've got complex logic that you expose through a plugin, you can design it so that logic is well encapsulated and isolated from most of the Neo4j-specific bits. Doing so should make unit testing much easier.

If the logic is straighforward, then there won't be much value in unit tests. Using either Docker or the even faster Test Harness (with JUnit integration) should fit the bill.

martijn
Node

The plugin is rather big, and does something unusual: there's a procedure that takes a url as a parameter, downloads an Excel (boo! hiss!) file from it, and imports that into neo4j in a specific way. Also a function that exports data to Excel and returns the url where it can be downloaded. So it's all side effects. Testing the return value of the function is useless; I want to test the method that generates the rows and columns. The logic is not straightforward and does involve neo4j interactions.

So far I'm unable to find any way to mock a Transaction for internal use, so it seems like my only option is to write my own mock, which would involve manually implementing a significant chunk of the neo4j api. I would really rather not do that. 

Can you at least share the injected fields and procedure signature? Do you want to unit test the whole procedure or some bits of it? If the latter, can you share these, or in pseudo-code at least?

It seems you're describing manual stubs as a solution.
What about using mocks with a library such as Mockito? You would be able to create the mocks very concisely and define only what's needed.

martijn
Node

Sure.

    @UserFunction(name = "excel.templated.export")
    public String excelExport(
            @Name("labels") List<String> periodNames,
            @Name("updateDefinition") Map<String, Object> updateDefinition
    ) {

and:

    @Procedure(name = "excel.templated.import", mode = Mode.WRITE)
    public Stream<BuiltInProcedures.NodeResult> excelImport(
            @Name("fileUri") String fileUri,
            @Name("updateDefinition") Map<String, Object> updateDefinition
    ) {

Most of the actual work is handled by other classes, and they still need access to the transaction.

I just checked that the class that generates the Excel does not need a Transaction, so that would be testable, if it wasn't for the fact that it also handled the upload. Maybe I should refactor that. But much of the logic about which nodes to collect and which ones become rows and columns, explicitly needs a Transaction to read from:

    private void exportPeriod(String periodName, ExcelGenerator exporter) {
        Result result = transaction.execute(QUERY),
            Map.of("periodName", periodName)
        );
        Sheet sheet = exporter.newSheet(periodName);

        int rowIndex = 0;
        while (result.hasNext()) {
            rowIndex++;
            Map<String, Object> resultRow = result.next();
            Row row = sheet.createRow(rowIndex);
            exportRow(resultRow, row);
        }
        createHeaderRow(sheet, headers);
    }

I suppose I could refactor this and mock the query results, but I also want to test that query.


@martijn wrote:

I also want to test that query.


Extract the query execution part and test it with Neo4j's test harness or TestContainers/Neo4j.

For the rest, I would indeed suggest refactoring the plugin so that test unit testing becomes easier ... OR test everything through slower integration/end-to-end tests.

I'll probably end up doing this. I'll also have a look at Mockito, but smaller pieces of code does make it more unit testable.