With Spring Boot and @DataNeo4jTest

Spring Boot 通过 org.springframework.boot:spring-boot-starter-test 提供 @DataNeo4jTest。后者包含 org.springframework.boot:spring-boot-test-autoconfigure,它包含注释和所需的底层代码。

Spring Boot offers @DataNeo4jTest through org.springframework.boot:spring-boot-starter-test. The latter brings in org.springframework.boot:spring-boot-test-autoconfigure which contains the annotation and the required infrastructure code.

Include Spring Boot Starter Test in a Maven build
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>
Include Spring Boot Starter Test in a Gradle build
dependencies {
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

@DataNeo4jTest 是一款 Spring Boot test slice。测试切片为使用 Neo4j 的测试提供了所有必要的设施:事务管理器、客户端、模板和声明存储库(根据是否有反应依赖项而决定为命令式或反应式变体)。测试切片已经包含 @ExtendWith(SpringExtension.class),因此它会随 JUnit 5(JUnit Jupiter)自动运行。

@DataNeo4jTest is a Spring Boot test slice. The test slice provides all the necessary infrastructure for tests using Neo4j: a transaction manager, a client, a template and declared repositories, in their imperative or reactive variants, depending on reactive dependencies present or not. The test slice already includes @ExtendWith(SpringExtension.class) so that it runs automatically with JUnit 5 (JUnit Jupiter).

默认情况下,@DataNeo4jTest 同时提供命令式和响应式设施,还添加了一个隐式的 @Transactional。但是,Spring 测试中的 @Transactional 始终表示命令式的事务,因为声明式事务需要通过方法的返回类型来确定是需要命令式的 PlatformTransactionManager 还是响应式的 ReactiveTransactionManager

@DataNeo4jTest provides both imperative and reactive infrastructure by default and also adds an implicit @Transactional as well. @Transactional in Spring tests however always means imperative transactional, as declarative transactions needs the return type of a method to decide whether the imperative PlatformTransactionManager or the reactive ReactiveTransactionManager is needed.

为了断言响应式资源库或服务的正确事务行为,您需要将 TransactionalOperator 注入到测试中,或将您的域逻辑包装在使用带注释方法的服务中,方法返回类型使得该基础设施可以选择正确的事务管理器。

To assert the correct transactional behaviour for reactive repositories or services, you will need to inject a TransactionalOperator into the test or wrap your domain logic in services that use annotated methods exposing a return type that makes it possible for the infrastructure to select the correct transaction manager.

测试切片不会引入嵌入式数据库或任何其他连接设置。使用适当的连接由您决定。

The test slice does not bring in an embedded database or any other connection setting. It is up to you to use an appropriate connection.

我们建议两个选项之一:使用 Neo4j Testcontainers module或 Neo4j 测试框架。Testcontainers 是一个知名项目,带有适用于许多不同服务的模块,而 Neo4j 测试框架不太为人所知。它是一个嵌入式实例,当测试存储过程中正如 Testing your Neo4j-based Java application中所述那样非常有用。但是也可以使用测试框架测试应用程序。因为它在与应用程序相同的 JVM 中启动了一个数据库,所以性能和计时可能与您的生产设置不同。

We recommend one of two options: either use the Neo4j Testcontainers module or the Neo4j test harness. While Testcontainers is a known project with modules for a lot of different services, Neo4j test harness is rather unknown. It is an embedded instance that is especially useful when testing stored procedures as described in Testing your Neo4j-based Java application. The test harness can however be used to test an application as well. As it brings up a database inside the same JVM as your application, performance and timings may not resemble your production setup.

为了方便您了解,我们提供了三种可能的方案,即 Neo4j 测试框架 3.5 和 4.x/5.x 以及 Testcontainers Neo4j。我们为 3.5 和 4.x/5.x 提供了不同的示例,因为两个版本之间的测试框架已发生变化。此外,4.0 要求使用 JDK 11。

For your convenience we provide three possible scenarios, Neo4j test harness 3.5 and 4.x/5.x as well as Testcontainers Neo4j. We provide different examples for 3.5 and 4.x/5.x as the test harness changed between those versions. Also, 4.0 requires JDK 11.

@DataNeo4jTest with Neo4j test harness 3.5

您需要以下依赖项来运行 [dataneo4jtest-harness35-example]

You need the following dependencies to run [dataneo4jtest-harness35-example]:

Neo4j 3.5 test harness dependencies
<dependency>
    <groupId>org.neo4j.test</groupId>
    <artifactId>neo4j-harness</artifactId>
    <version>{docs-neo4j-3-version}</version>
    <scope>test</scope>
</dependency>

Neo4j 3.5 企业版的依赖项可在 com.neo4j.test:neo4j-harness-enterprise 和适当的资源库配置下获得。

The dependencies for the enterprise version of Neo4j 3.5 are available under the com.neo4j.test:neo4j-harness-enterprise and an appropriate repository configuration.

Using Neo4j 3.5 test harness
import static org.assertj.core.api.Assertions.assertThat;

import java.util.Optional;

import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.neo4j.harness.ServerControls;
import org.neo4j.harness.TestServerBuilders;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.data.neo4j.DataNeo4jTest;
import org.springframework.data.neo4j.core.Neo4jClient;
import org.springframework.test.context.DynamicPropertyRegistry;
import org.springframework.test.context.DynamicPropertySource;

@DataNeo4jTest
class MovieRepositoryTest {

	private static ServerControls embeddedDatabaseServer;

	@BeforeAll
	static void initializeNeo4j() {

		embeddedDatabaseServer = TestServerBuilders.newInProcessBuilder() (1)
			.newServer();
	}

	@AfterAll
	static void stopNeo4j() {

		embeddedDatabaseServer.close(); (2)
	}

	@DynamicPropertySource  (3)
	static void neo4jProperties(DynamicPropertyRegistry registry) {

		registry.add("spring.neo4j.uri", embeddedDatabaseServer::boltURI);
		registry.add("spring.neo4j.authentication.username", () -> "neo4j");
		registry.add("spring.neo4j.authentication.password", () -> null);
	}

	@Test
	public void findSomethingShouldWork(@Autowired Neo4jClient client) {

		Optional<Long> result = client.query("MATCH (n) RETURN COUNT(n)")
			.fetchAs(Long.class)
			.one();
		assertThat(result).hasValue(0L);
	}
}
1 Entrypoint to create an embedded Neo4j
2 This is a Spring Boot annotation that allows for dynamically registered application properties. We overwrite the corresponding Neo4j settings.
3 Shutdown Neo4j after all tests.

@DataNeo4jTest with Neo4j test harness 4.x/5.x

您需要以下依赖项来运行 [dataneo4jtest-harness40-example]

You need the following dependencies to run [dataneo4jtest-harness40-example]:

Neo4j 4.x test harness dependencies
<dependency>
    <groupId>org.neo4j.test</groupId>
    <artifactId>neo4j-harness</artifactId>
    <version>{docs-neo4j-4-version}</version>
    <scope>test</scope>
    <exclusions>
        <exclusion>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-nop</artifactId>
        </exclusion>
    </exclusions>
</dependency>

Neo4j 4.x/5.x 企业版的依赖项可在 com.neo4j.test:neo4j-harness-enterprise 和适当的资源库配置下获得。

The dependencies for the enterprise version of Neo4j 4.x/5.x are available under the com.neo4j.test:neo4j-harness-enterprise and an appropriate repository configuration.

Using Neo4j 4.x/5.x test harness
import static org.assertj.core.api.Assertions.assertThat;

import java.util.Optional;

import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.neo4j.harness.Neo4j;
import org.neo4j.harness.Neo4jBuilders;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.data.neo4j.DataNeo4jTest;
import org.springframework.data.neo4j.core.Neo4jClient;
import org.springframework.test.context.DynamicPropertyRegistry;
import org.springframework.test.context.DynamicPropertySource;

@DataNeo4jTest
class MovieRepositoryTest {

	private static Neo4j embeddedDatabaseServer;

	@BeforeAll
	static void initializeNeo4j() {

		embeddedDatabaseServer = Neo4jBuilders.newInProcessBuilder() (1)
			.withDisabledServer() (2)
			.build();
	}

	@DynamicPropertySource (3)
	static void neo4jProperties(DynamicPropertyRegistry registry) {

		registry.add("spring.neo4j.uri", embeddedDatabaseServer::boltURI);
		registry.add("spring.neo4j.authentication.username", () -> "neo4j");
		registry.add("spring.neo4j.authentication.password", () -> null);
	}

	@AfterAll
	static void stopNeo4j() {

		embeddedDatabaseServer.close(); (4)
	}

	@Test
	public void findSomethingShouldWork(@Autowired Neo4jClient client) {

		Optional<Long> result = client.query("MATCH (n) RETURN COUNT(n)")
			.fetchAs(Long.class)
			.one();
		assertThat(result).hasValue(0L);
	}
}
1 Entrypoint to create an embedded Neo4j
2 Disable the unneeded Neo4j HTTP server
3 This is a Spring Boot annotation that allows for dynamically registered application properties. We overwrite the corresponding Neo4j settings.
4 Shut down Neo4j after all tests.

@DataNeo4jTest with Testcontainers Neo4j

当然,配置连接的原则是与 Testcontainers 中所示的相同,如 [dataneo4jtest-testcontainers-example] 中所示。您需要以下依赖项:

The principal of configuring the connection is of course still the same with Testcontainers as shown in [dataneo4jtest-testcontainers-example]. You need the following dependencies:

<dependency>
    <groupId>org.testcontainers</groupId>
    <artifactId>neo4j</artifactId>
    <version>1.17.6</version>
    <scope>test</scope>
</dependency>

以及一个完整的测试:

And a complete test:

Using Test containers
import static org.assertj.core.api.Assertions.assertThat;

import java.util.Optional;

import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.data.neo4j.DataNeo4jTest;
import org.springframework.data.neo4j.core.Neo4jClient;
import org.springframework.test.context.DynamicPropertyRegistry;
import org.springframework.test.context.DynamicPropertySource;
import org.testcontainers.containers.Neo4jContainer;

@DataNeo4jTest
class MovieRepositoryTCTest {

	private static Neo4jContainer<?> neo4jContainer;

	@BeforeAll
	static void initializeNeo4j() {

		neo4jContainer = new Neo4jContainer<>()
			.withAdminPassword("somePassword");
		neo4jContainer.start();
	}

	@AfterAll
	static void stopNeo4j() {

		neo4jContainer.close();
	}

	@DynamicPropertySource
	static void neo4jProperties(DynamicPropertyRegistry registry) {

		registry.add("spring.neo4j.uri", neo4jContainer::getBoltUrl);
		registry.add("spring.neo4j.authentication.username", () -> "neo4j");
		registry.add("spring.neo4j.authentication.password", neo4jContainer::getAdminPassword);
	}

	@Test
	public void findSomethingShouldWork(@Autowired Neo4jClient client) {

		Optional<Long> result = client.query("MATCH (n) RETURN COUNT(n)")
			.fetchAs(Long.class)
			.one();
		assertThat(result).hasValue(0L);
	}
}

Alternatives to a @DynamicPropertySource

在某些情况下,上述注释不适合您的用例。其中一种情况可能是您希望 100% 地控制驱动程序的初始化方式。通过运行测试容器,您可以使用类似这样的嵌套静态配置类来完成此操作:

There are some scenarios in which the above annotation does not fit your use case. One of those might be that you want to have 100% control over how the driver is initialized. With a test container running, you could do this with a nested, static configuration class like this:

@TestConfiguration(proxyBeanMethods = false)
static class TestNeo4jConfig {

    @Bean
    Driver driver() {
        return GraphDatabase.driver(
        		neo4jContainer.getBoltUrl(),
        		AuthTokens.basic("neo4j", neo4jContainer.getAdminPassword())
        );
    }
}

如果您希望使用属性但无法使用 @DynamicPropertySource,则可以使用初始值设定项:

If you want to use the properties but cannot use a @DynamicPropertySource, you would use an initializer:

Alternative injection of dynamic properties
@ContextConfiguration(initializers = PriorToBoot226Test.Initializer.class)
@DataNeo4jTest
class PriorToBoot226Test {

    private static Neo4jContainer<?> neo4jContainer;

    @BeforeAll
    static void initializeNeo4j() {

        neo4jContainer = new Neo4jContainer<>()
            .withAdminPassword("somePassword");
        neo4jContainer.start();
    }

    @AfterAll
    static void stopNeo4j() {

        neo4jContainer.close();
    }

    static class Initializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
        public void initialize(ConfigurableApplicationContext configurableApplicationContext) {
            TestPropertyValues.of(
                "spring.neo4j.uri=" + neo4jContainer.getBoltUrl(),
                "spring.neo4j.authentication.username=neo4j",
                "spring.neo4j.authentication.password=" + neo4jContainer.getAdminPassword()
            ).applyTo(configurableApplicationContext.getEnvironment());
        }
    }
}