搭配 Spring Boot 與 @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 測試工具組。雖然 Testcontainers 是一個知名的專案,具有許多不同服務的模組,但 Neo4j 測試工具組相對不為人知。它是一個嵌入式實例,在測試預存程序時特別有用,如 測試您的基於 Neo4j 的 Java 應用程式 中所述。然而,測試工具組也可用於測試應用程式。由於它在與您的應用程式相同的 JVM 內啟動資料庫,因此效能和時序可能與您的生產環境設定不同。
為了您的方便,我們提供了三種可能的場景:Neo4j 測試工具組 3.5 和 4.x/5.x,以及 Testcontainers Neo4j。我們為 3.5 和 4.x/5.x 提供了不同的範例,因為測試工具組在這兩個版本之間發生了變化。此外,4.0 需要 JDK 11。
@DataNeo4jTest
搭配 Neo4j 測試工具組 3.5
您需要以下相依性才能運行 使用 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);
}
}
1 | 建立嵌入式 Neo4j 的入口點 |
2 | 這是一個 Spring Boot 註解,允許動態註冊應用程式屬性。我們覆寫相應的 Neo4j 設定。 |
3 | 在所有測試後關閉 Neo4j。 |
@DataNeo4jTest
搭配 Neo4j 測試工具組 4.x/5.x
您需要以下相依性才能運行 使用 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);
}
}
1 | 建立嵌入式 Neo4j 的入口點 |
2 | 停用不需要的 Neo4j HTTP 伺服器 |
3 | 這是一個 Spring Boot 註解,允許動態註冊應用程式屬性。我們覆寫相應的 Neo4j 設定。 |
4 | 在所有測試後關閉 Neo4j。 |
@DataNeo4jTest
搭配 Testcontainers Neo4j
使用 Testcontainers 配置連線的原則當然與 使用 Test containers 中所示相同。您需要以下相依性
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>neo4j</artifactId>
<version>1.17.6</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.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);
}
}
@DynamicPropertySource
的替代方案
在某些情況下,上述註解可能不適合您的用例。其中一種可能是您希望 100% 控制驅動程式的初始化方式。在測試容器運行的情況下,您可以使用像這樣的巢狀靜態配置類來實現這一點
@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());
}
}
}