跳到主要内容

使用 Spring Boot 和 @DataNeo4jTest

DeepSeek V3 中英对照 With Spring Boot and @DataNeo4jTest With Spring Boot and @DataNeo4jTest

Spring Boot 通过 org.springframework.boot:spring-boot-starter-test 提供了 @DataNeo4jTest。后者引入了 org.springframework.boot:spring-boot-test-autoconfigure,其中包含了该注解以及所需的基础设施代码。

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
xml
dependencies {
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
groovy

@DataNeo4jTest 是 Spring Boot 的一个测试切片。该测试切片为使用 Neo4j 的测试提供了所有必要的基础设施:事务管理器、客户端、模板以及声明的存储库,这些组件可以是命令式或响应式的,具体取决于是否存在响应式依赖项。该测试切片已经包含了 @ExtendWith(SpringExtension.class),因此它会自动与 JUnit 5(JUnit Jupiter)一起运行。

@DataNeo4jTest 默认提供了命令式和响应式的基础设施,并且还隐式地添加了 @Transactional 注解。然而,在 Spring 测试中,@Transactional 始终意味着命令式事务,因为声明式事务需要方法的返回类型来决定是需要命令式的 PlatformTransactionManager 还是响应式的 ReactiveTransactionManager

为了确保响应式仓库或服务的正确事务行为,你需要将 TransactionalOperator 注入到测试中,或者将你的领域逻辑封装在使用注解方法的服务中,这些方法暴露的返回类型使得基础设施能够选择正确的事务管理器。

测试切片不会引入嵌入式数据库或任何其他连接设置。你需要自行使用适当的连接。

我们推荐以下两种选择之一:使用 Neo4j Testcontainers 模块 或 Neo4j 测试工具。虽然 Testcontainers 是一个知名的项目,提供了许多不同服务的模块,但 Neo4j 测试工具则相对不为人所知。它是一个嵌入式实例,特别适合在测试存储过程时使用,如 Testing your Neo4j-based Java application 中所述。不过,测试工具也可以用于测试应用程序。由于它在与应用程序相同的 JVM 中启动数据库,因此性能和时间可能与生产环境不一致。

为了方便您使用,我们提供了三种可能的场景:Neo4j 测试工具 3.5 和 4.x/5.x 以及 Testcontainers Neo4j。由于测试工具在这些版本之间有所变化,我们为 3.5 和 4.x/5.x 提供了不同的示例。此外,4.0 版本需要 JDK 11。

使用 Neo4j 测试工具 3.5 的 @DataNeo4jTest

你需要以下依赖项来运行 使用 Neo4j 3.5 测试工具:

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

Neo4j 3.5 企业版的依赖项可通过 com.neo4j.test:neo4j-harness-enterprise 和适当的仓库配置获取。

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);
}
}
java
  • 创建嵌入式 Neo4j 的入口点

  • 这是一个 Spring Boot 注解,允许动态注册应用程序属性。我们覆盖了相应的 Neo4j 设置。

  • 在所有测试结束后关闭 Neo4j。

使用 Neo4j 测试工具 4.x/5.x 的 @DataNeo4jTest

你需要以下依赖项来运行使用 Neo4j 4.x/5.x 测试工具:

<dependency>
<groupId>org.neo4j.test</groupId>
<artifactId>neo4j-harness</artifactId>
<version>4.4.25</version>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-nop</artifactId>
</exclusion>
</exclusions>
</dependency>
xml

企业版 Neo4j 4.x/5.x 的依赖项可在 com.neo4j.test:neo4j-harness-enterprise 下获取,并需要配置适当的仓库。

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);
}
}
java
  • 创建嵌入式 Neo4j 的入口点

  • 禁用不需要的 Neo4j HTTP 服务器

  • 这是一个 Spring Boot 注解,允许动态注册应用程序属性。我们覆盖了相应的 Neo4j 设置。

  • 在所有测试完成后关闭 Neo4j。

使用 Testcontainers Neo4j 的 @DataNeo4jTest

配置连接的原则当然与 Testcontainers 相同,如使用 Testcontainers 所示。你需要以下依赖项:

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

完整的测试:

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);
}
}
java

@DynamicPropertySource 的替代方案

在某些场景下,上述注解可能并不适合你的使用情况。其中一个可能是你希望完全控制驱动程序的初始化方式。在测试容器运行时,你可以通过一个嵌套的静态配置类来实现这一点,如下所示:

@TestConfiguration(proxyBeanMethods = false)
static class TestNeo4jConfig {

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

如果你想使用属性但不能使用 @DynamicPropertySource,你可以使用初始化器:

@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());
}
}
}
java