使用 Spring Boot 与 @DataNeo4jTest
@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>
dependencies {
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
@DataNeo4jTest 是 Spring Boot 的一个测试切片。该测试切片为使用 Neo4j 的测试提供了全部必要的基础设施:一个事务管理器、一个客户端、一个模板以及所声明的仓库,并根据是否存在响应式依赖,提供其命令式或响应式变体。该测试切片已包含 @ExtendWith(SpringExtension.class),因此可在 JUnit 5(JUnit Jupiter)下自动运行。
@DataNeo4jTest 默认同时提供命令式和响应式基础设施,并且还隐式添加了 @Transactional。不过,Spring 测试中的 @Transactional 始终意味着命令式事务,因为声明式事务需要根据方法的返回类型来决定使用命令式的 PlatformTransactionManager 还是响应式的 ReactiveTransactionManager。
要断言响应式仓库或服务的正确事务行为,您需要将 TransactionalOperator 注入测试中,或将领域逻辑封装在使用注解方法的服务中,这些方法的返回类型要能让基础设施选择正确的事务管理器。
该测试切片不会引入内嵌数据库或任何其他连接设置。由您自己决定使用合适的连接。
我们推荐以下两种方案之一:使用 Neo4j Testcontainers 模块 或 Neo4j 测试框架(test harness)。Testcontainers 是一个知名项目,为许多不同的服务提供模块,而 Neo4j 测试框架则相对鲜为人知。它是一个内嵌实例,在测试存储过程时尤其有用,如测试基于 Neo4j 的 Java 应用程序所述。不过,该测试框架也可用于测试应用程序。由于它在与应用程序相同的 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>
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);
}
}
创建内嵌 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>
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);
}
}
创建内嵌 Neo4j 的入口点
禁用不需要的 Neo4j HTTP 服务器
这是一个 Spring Boot 注解,允许动态注册应用程序属性。我们覆盖了相应的 Neo4j 设置。
在所有测试结束后关闭 Neo4j。
配合 Testcontainers Neo4j 使用 @DataNeo4jTest
在使用 Testcontainers 时,配置连接的原理当然仍然相同,如使用测试容器所示。您需要以下依赖:
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers-neo4j</artifactId>
<version>${testcontainers.version}</version>
<scope>test</scope>
</dependency>
以及一个完整的测试:
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.neo4j.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);
}
}
@DynamicPropertySource 的替代方案
在某些场景下,上述注解并不适合您的用例。其中一种可能是:您希望完全控制驱动程序的初始化方式。在运行测试容器时,您可以像这样使用一个嵌套的静态配置类来实现:
@TestConfiguration(proxyBeanMethods = false)
static class TestNeo4jConfig {
@Bean
Driver driver() {
return GraphDatabase.driver(
neo4jContainer.getBoltUrl(),
AuthTokens.basic("neo4j", neo4jContainer.getAdminPassword())
);
}
}
如果您想使用这些属性但不能使用 @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());
}
}
}