Есть ли тестовый жгут 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)?