Есть ли тестовый жгут Neo4j, в котором используется модель расширения JUnit 5?

При написании тестовых примеров для Neo4j я хотел бы перейти на использование только JUnit 5 Extension Model и не использовать org.junit.vintage или же junit-jupiter-migrationsupport, В настоящее время я могу найти только тестовый комплект Neo4j для JUnit 4, который использует TestRule и зависит от org.junit.vintage а также junit-jupiter-migrationsupport,

Есть ли тестовый жгут Neo4j для JUnit 5, использующий модель расширения?

Рекомендации:
Neo4j: Главная, GitHub
Neo4j test-harness: Maven, GitHub, pom.xml
JUnit 4: GitHub
Юнит 4 TestRule: Руководство по JUnit 4, API JUnit 4.12, Neo4jRule GitHub
JUnit 5: GitHub
Юнит 5 Extension Model: Руководство пользователя JUnit 5, GitHub
Юнит 5 org.junit.vintage: Руководство пользователя JUnit 5, Test-harness pom.xml
Юнит 5 junit-jupiter-migrationsupport: Руководство пользователя JUnit 5, Test-harness pom.xml


Я знаю, что можно использовать JUnit 4 и JUnit 5 в смешанной среде, например, смешивая тесты JUnit 4 и JUnit 5.

Я начал писать свои собственные расширения Neo4j JUnit 5 с помощью " Руководства по расширениям JUnit 5", но если уже существует стандартный тестовый комплект Neo4j с моделью расширения JUnit 5, зачем создавать свои собственные.

Может быть, я просто запрашиваю неправильные ключевые слова, которые просто neo4j а также JUnit 5 но продолжают появляться те же самые результаты, ни один из которых не приводит к тому, что я ищу.

Проверил расширения JUnit Jupiter и не нашел ни одного для Neo4j.

РЕДАКТИРОВАТЬ

Доказательство концепции

Поскольку приведенный ниже код является лишь подтверждением концепции, он не публикуется в качестве принятого ответа, но, надеюсь, будет через несколько дней.

Оказывается, что добавление JUnit 5 Jupiter Extensions к существующему JUnit TestRlue не так уж и плохо. На этом пути было несколько трудных моментов, и если вы похожи на меня и не живете и не дышите одним языком программирования или набором инструментов, вам нужно некоторое время, чтобы понять дух; это должно быть ТАКИЕ теги, если вы спросите меня.

Примечание. Этот код представляет собой комбинацию некоторого кода из Neo4j TestRule и Руководства по расширениям JUnit 5.

Начиная с Neo4j TestRule, просто меняйте навесное оборудование:
Удалить TestRule
добавлять BeforeEachCallback а также AfterEachCallback

Замечания: BeforeEach а также AfterEach используются вместо BeforeAll а также AfterAll с Neo4j, потому что при каждом новом тесте при создании узлов, если новый узел создается так же, как и предыдущий тест, а база данных не является новой базой данных, проверка идентификатора узла будет отличаться, потому что новый узел создается для каждого теста и получает другой идентификатор. Поэтому, чтобы избежать этой проблемы и сделать это так же, как это делается с Neo4j TestRule, для каждого экземпляра теста создается новая база данных. Я смотрел на сброс базы данных между тестами, но кажется, что единственный способ сделать это - удалить все файлы, которые составляют базу данных.:(

/*
 * Copyright (c) 2002-2018 "Neo4j,"
 * Neo4j Sweden AB [http://neo4j.com]
 *
 * This file is part of Neo4j.
 *
 * Neo4j is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
//package org.neo4j.harness.junit;
package org.egt.neo4j.harness.example_002.junit;

// References:
// GitHub - junit-team - junit5 - junit5/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine - https://github.com/junit-team/junit5/tree/releases/5.3.x/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension

// Notes:
// With JUnit 4 TestRule there was basically one rule that was called at multiple points and for multiple needs.
// With JUnit 5 Extensions the calls are specific to a lifecycle step, e.g. BeforeAll, AfterEach,
// or specific to a need, e.g. Exception handling, maintaining state across test,
// so in JUnit 4 where a single TestRule could be created in JUnit5 many Extensions need to be created.
// Another major change is that with JUnit 4 a rule would wrap around a test which would make
// implementing a try/catch easy, with JUnit 5 the process is broken down into a before and after callbacks
// that make this harder, however because the extensions can be combined for any test,
// adding the ability to handle exceptions does not require adding the code to every extension,
// but merely adding the extension to the test. (Verify this).

import java.io.File;
import java.io.PrintStream;
import java.util.function.Function;

import org.junit.jupiter.api.extension.*;
import org.neo4j.graphdb.GraphDatabaseService;
import org.neo4j.graphdb.config.Setting;

import org.egt.neo4j.harness.example_002.ServerControls;
import org.egt.neo4j.harness.example_002.TestServerBuilder;
import org.egt.neo4j.harness.example_002.TestServerBuilders;

/**
 * A convenience wrapper around {@link org.neo4j.harness.TestServerBuilder}, exposing it as a JUnit
 * {@link org.junit.Rule rule}.
 *
 * Note that it will try to start the web server on the standard 7474 port, but if that is not available
 * (typically because you already have an instance of Neo4j running) it will try other ports. Therefore it is necessary
 * for the test code to use {@link #httpURI()} and then {@link java.net.URI#resolve(String)} to create the URIs to be invoked.
 */
//public class Neo4jRule implements TestRule, TestServerBuilder
public class Neo4jDatabaseSetupExtension implements  BeforeEachCallback, AfterEachCallback, TestServerBuilder
{
    private TestServerBuilder builder;
    private ServerControls controls;
    private PrintStream dumpLogsOnFailureTarget;

    Neo4jDatabaseSetupExtension(TestServerBuilder builder )
    {
        this.builder = builder;
    }

    public Neo4jDatabaseSetupExtension( )
    {
        this( TestServerBuilders.newInProcessBuilder() );
    }

    public Neo4jDatabaseSetupExtension(File workingDirectory )
    {
        this( TestServerBuilders.newInProcessBuilder( workingDirectory ) );
    }

    @Override
    public void afterEach(ExtensionContext context) throws Exception {

        if (controls != null)
        {
            controls.close();
        }
    }

    @Override
    public void beforeEach(ExtensionContext context) throws Exception {
        controls = builder.newServer();
    }

    @Override
    public ServerControls newServer() {
        throw new UnsupportedOperationException( "The server cannot be manually started via this class, it must be used as a JUnit 5 Extension." );
    }

    @Override
    public TestServerBuilder withConfig(Setting<?> key, String value) {
        builder = builder.withConfig( key, value );
        return this;
    }

    @Override
    public TestServerBuilder withConfig(String key, String value) {
        builder = builder.withConfig( key, value );
        return this;
    }

    @Override
    public TestServerBuilder withExtension(String mountPath, Class<?> extension) {
        builder = builder.withExtension( mountPath, extension );
        return this;
    }

    @Override
    public TestServerBuilder withExtension(String mountPath, String packageName) {
        builder = builder.withExtension( mountPath, packageName );
        return this;
    }

    @Override
    public TestServerBuilder withFixture(File cypherFileOrDirectory) {
        builder = builder.withFixture( cypherFileOrDirectory );
        return this;
    }

    @Override
    public TestServerBuilder withFixture(String fixtureStatement) {
        builder = builder.withFixture( fixtureStatement );
        return this;
    }

    @Override
    public TestServerBuilder withFixture(Function<GraphDatabaseService, Void> fixtureFunction) {
        builder = builder.withFixture( fixtureFunction );
        return this;
    }

    @Override
    public TestServerBuilder copyFrom(File sourceDirectory) {
        builder = builder.copyFrom( sourceDirectory );
        return this;
    }

    @Override
    public TestServerBuilder withProcedure(Class<?> procedureClass) {
        builder = builder.withProcedure( procedureClass );
        return this;
    }

    @Override
    public TestServerBuilder withFunction(Class<?> functionClass) {
        builder = builder.withFunction( functionClass );
        return this;
    }

    @Override
    public TestServerBuilder withAggregationFunction(Class<?> functionClass) {
        builder = builder.withAggregationFunction( functionClass );
        return this;
    }
}

Затем, чтобы каждый экземпляр теста имел новый GraphDatabaseService который создан с ServerControls реализовать JUnit 5 ParameterResolver.

package org.egt.neo4j.harness.example_002.junit;

import org.egt.neo4j.harness.example_002.ServerControls;
import org.egt.neo4j.harness.example_002.TestServerBuilders;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.ParameterContext;
import org.junit.jupiter.api.extension.ParameterResolutionException;
import org.junit.jupiter.api.extension.ParameterResolver;

public class Neo4jDatabaseParameterResolver implements ParameterResolver {

    @Override
    public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException {
        boolean result = parameterContext.getParameter()
                .getType()
                .equals(ServerControls.class);

        return result;
    }

    @Override
    public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException {

        Object result = (ServerControls)TestServerBuilders.newInProcessBuilder().newServer();

        return result;
    }
}

Наконец, осталось только использовать модель расширения Neo4j JUnit 5 с @ExtendWith а также @Test:

package org.egt.example_002;

import org.egt.neo4j.harness.example_002.ServerControls;
import org.egt.neo4j.harness.example_002.junit.Neo4jDatabaseParameterResolver;
import org.egt.neo4j.harness.example_002.junit.Neo4jDatabaseSetupExtension;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;

import org.neo4j.graphdb.GraphDatabaseService;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.Transaction;

import static org.junit.jupiter.api.Assertions.assertEquals;

@ExtendWith({ Neo4jDatabaseSetupExtension.class, Neo4jDatabaseParameterResolver.class })
public class Neo4jUnitTests {

    private ServerControls sc;
    private GraphDatabaseService graphDb;

    public Neo4jUnitTests(ServerControls sc) {
        this.sc = sc;
        this.graphDb = sc.graph();
    }

    @Test
    public void shouldCreateNode()
    {
        // START SNIPPET: unitTest
        Node n;
        try ( Transaction tx = graphDb.beginTx() )
        {
            n = graphDb.createNode();
            n.setProperty( "name", "Nancy" );
            tx.success();
        }

        long id = n.getId();
        // The node should have a valid id
        assertEquals(0L, n.getId());

        // Retrieve a node by using the id of the created node. The id's and
        // property should match.
        try ( Transaction tx = graphDb.beginTx() )
        {
            Node foundNode = graphDb.getNodeById( n.getId() );
            assertEquals( foundNode.getId(),  n.getId() );
            assertEquals( "Nancy" , (String)foundNode.getProperty("name") );
        }
        // END SNIPPET: unitTest

    }
}

Одна вещь, которую я узнал в процессе, заключается в том, что код TestRule кажется do everything in one class в то время как новая модель расширений использует много расширений, чтобы делать то же самое. Таким образом, регистрация, обработка исключений и другие вещи, которые есть в Neo4j TestRule, не являются этим доказательством концепции. Однако поскольку модель расширений позволяет смешивать и сопоставлять расширения, добавить запись в журнал и обработку исключений может быть так же просто, как использовать расширение из другого места и просто добавить @ExtendWith вот почему я не создал их для этого доказательства концепции.

Также вы заметите, что я меняю имена пакетов, которые я сделал только для того, чтобы избежать столкновений с другим кодом в том же проекте, который реализует другие части кода в одиночку, чтобы я мог перейти к этому рабочему доказательству концепции,

Наконец, я не удивлюсь, если класс JUnit 4 Neo4j TestRule и класс JUnit 5 Extension Model могут наследоваться от базового класса и затем быть доступными в одном и том же тестовом пакете; пальцы скрещены. Очевидно, что большая часть базового класса будет извлечена из класса Neo4j TestRule.

1 ответ

Самый простой способ - вообще не использовать расширение.

Используйте следующие зависимости для Neo4j 4.x:

<dependency>
    <groupId>org.neo4j.test</groupId>
    <artifactId>neo4j-harness</artifactId>
    <version>4.0.8</version>
    <scope>test</scope>
</dependency>

А затем структурируйте свой тест JUnit 5 следующим образом:

import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.neo4j.harness.Neo4j;
import org.neo4j.harness.Neo4jBuilders;

public class SimpleTest {

    private static Neo4j embeddedDatabaseServer;

    @BeforeAll
    static void initializeNeo4j() {

        embeddedDatabaseServer = Neo4jBuilders.newInProcessBuilder()
            .withDisabledServer() // Don't need Neos HTTP server
            .withFixture(""
                + "CREATE (TheMatrix:Movie {title:'The Matrix', released:1999, tagline:'Welcome to the Real World'})"
            )
            .build();
    }

    @AfterAll
    static void stopNeo4j() {

        embeddedDatabaseServer.close();
    }

    @Test
    void testSomething() {

        try(var tx = embeddedDatabaseServer.databaseManagementService().database("neo4j").beginTx()) {
            var result = tx.execute("MATCH (m:Movie) WHERE m.title = 'The Matrix' RETURN m.released");
            Assertions.assertEquals(1999L, result.next().get("m.released"));
        }
    }
}

Конечно, вы также можете открыть URL-адрес для встроенного экземпляра. embeddedDatabaseServer.boltURI()дает вам адрес локального сокета. Аутентификация отключена.

Тест будет выглядеть так:

@Test
void testSomethingOverBolt() {

    try(var driver = GraphDatabase.driver(embeddedDatabaseServer.boltURI(), AuthTokens.none());
    var session = driver.session()) {
        var result = session.run("MATCH (m:Movie) WHERE m.title = 'The Matrix' RETURN m.released");
        Assertions.assertEquals(1999L, result.next().get("m.released").asLong());
    }
}

Конечно, вам понадобится org.neo4j.driver:neo4j-java-driver для этого.

Если вам нужен нестатический экземпляр встроенного сервера, вы можете смоделировать весь тестовый класс следующим образом:

import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
import org.neo4j.harness.Neo4j;
import org.neo4j.harness.Neo4jBuilders;

@TestInstance(TestInstance.Lifecycle.PER_CLASS)
public class SimpleTest {

    private final Neo4j embeddedDatabaseServer = Neo4jBuilders.newInProcessBuilder()
            .withDisabledServer() // Don't need Neos HTTP server
        .withFixture(""
            + "CREATE (TheMatrix:Movie {title:'The Matrix', released:1999, tagline:'Welcome to the Real World'})"
        )
        .build();

    @AfterAll
    void stopNeo4j() {

        embeddedDatabaseServer.close();
    }

    @Test
    void whatever() {
    }
}

Обратите внимание на @TestInstance(TestInstance.Lifecycle.PER_CLASS) поверх тестового класса и нестатического @AfterAll метод.

Не совсем ответ, но у меня есть класс Neo4jExtension, который является расширением JUnit 5.
Мой в основном жестко запрограммирован, потому что я хотел что-то, что быстро работает для меня.
Он создает встроенную базу данных Neo4j с болтовым соединителем.
Он также загружает некоторые процедуры и функции apoc и загружает исходные данные для тестов.
Ваш подход интереснее.

import lombok.extern.slf4j.Slf4j;
import org.apache.catalina.webresources.TomcatURLStreamHandlerFactory;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.junit.jupiter.api.extension.AfterAllCallback;
import org.junit.jupiter.api.extension.AfterEachCallback;
import org.junit.jupiter.api.extension.BeforeAllCallback;
import org.junit.jupiter.api.extension.BeforeEachCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.ParameterContext;
import org.junit.jupiter.api.extension.ParameterResolutionException;
import org.junit.jupiter.api.extension.ParameterResolver;
import org.neo4j.graphdb.GraphDatabaseService;
import org.neo4j.graphdb.Transaction;
import org.neo4j.graphdb.factory.GraphDatabaseFactory;
import org.neo4j.graphdb.factory.GraphDatabaseSettings;
import org.neo4j.internal.kernel.api.exceptions.KernelException;
import org.neo4j.kernel.configuration.BoltConnector;
import org.neo4j.kernel.configuration.Settings;
import org.neo4j.kernel.impl.proc.Procedures;
import org.neo4j.kernel.internal.GraphDatabaseAPI;

import java.io.File;
import java.util.List;

import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.Arrays.asList;
import static org.neo4j.helpers.ListenSocketAddress.listenAddress;
import static org.neo4j.kernel.configuration.BoltConnector.EncryptionLevel.DISABLED;
import static org.neo4j.kernel.configuration.Connector.ConnectorType.BOLT;
import static org.neo4j.kernel.configuration.Settings.FALSE;
import static org.neo4j.kernel.configuration.Settings.STRING;
import static org.neo4j.kernel.configuration.Settings.TRUE;

@Slf4j
public class Neo4jExtension implements
        BeforeAllCallback, AfterAllCallback, ParameterResolver,
        BeforeEachCallback, AfterEachCallback {

    private static final File DB_PATH = new File("target/neo4j-test");

    private GraphDatabaseService graphDb;
    private Transaction currentTransaction;

    @Override
    public void beforeAll(ExtensionContext extensionContext) throws Exception {
        FileUtils.deleteDirectory(DB_PATH);
        TomcatURLStreamHandlerFactory.disable();
        final BoltConnector boltConnector = new BoltConnector("bolt");
        graphDb = new GraphDatabaseFactory()
                .newEmbeddedDatabaseBuilder(DB_PATH)
                .setConfig(Settings.setting("dbms.directories.import", STRING, "data"),"../../data")
                .setConfig(Settings.setting("dbms.security.procedures.unrestricted", STRING, "apoc.*"),"apoc.*")
                .setConfig(boltConnector.type, BOLT.name())
                .setConfig(boltConnector.enabled, TRUE)
                .setConfig(boltConnector.listen_address, listenAddress("127.0.0.1", 7676))
                .setConfig(boltConnector.encryption_level, DISABLED.name())
                .setConfig(GraphDatabaseSettings.auth_enabled, FALSE)
                .newGraphDatabase();
        Procedures procedures = ((GraphDatabaseAPI) graphDb).getDependencyResolver().resolveDependency(Procedures.class);
        List<Class<?>> apocProcedures = asList(apoc.convert.Json.class);
        apocProcedures.forEach((procedure) -> {
            try {
                procedures.registerFunction(procedure);
                procedures.registerProcedure(procedure);
            } catch (KernelException e) {
                e.printStackTrace();
            }
        });
        final String importScript = FileUtils.readFileToString(new File("data/import_data.cql"), UTF_8);
        final String[] split = importScript.split(";");
        for (String query : split) {
            if (StringUtils.isNotBlank(query)) {
                graphDb.execute(query);
            }
        }
    }

    @Override
    public void afterAll(ExtensionContext extensionContext) throws Exception {
        graphDb.shutdown();
    }

    @Override
    public void beforeEach(ExtensionContext extensionContext) throws Exception {
        currentTransaction = graphDb.beginTx();
    }

    @Override
    public void afterEach(ExtensionContext extensionContext) throws Exception {
        currentTransaction.failure();
        currentTransaction.close();
    }

    @Override
    public boolean supportsParameter(ParameterContext parameterContext,
                                     ExtensionContext extensionContext) throws ParameterResolutionException {
        return parameterContext.getParameter().getType().equals(GraphDatabaseService.class);
    }

    @Override
    public Object resolveParameter(ParameterContext parameterContext,
                                   ExtensionContext extensionContext) throws ParameterResolutionException {
        return graphDb;
    }
}

Обновление: люди, близкие к экосистеме Neo4J/Spring Data, не рекомендовали использоватьNeo4jExtention. См. Этот ответ для более подробной информации.


Сейчас (с января 2019 г.) Neo4jExtensionдля JUnit 5, который делает то, чтоNeo4jRule делал для Junit 4.

Вы используете это так:

@ExtendWith(org.neo4j.harness.junit.extension.Neo4jExtension.class)
class MyTest {
    ...

Если вы используете Spring / Spring Data Neo4j, вы можете обнаружить, что это расширение не очень хорошо работает с SpringExtension. Я описал, как решить эту проблему, в другом разделе вопросов и ответов: Как мне настроить интеграционный тест Spring Data Neo4j с JUnit 5 (в Kotlin)?

Другие вопросы по тегам