Spring Boot、@DataNeo4jTest の場合

Spring Boot は @DataNeo4jTest から org.springframework.boot:spring-boot-starter-test まで提供します。後者は、アノテーションと必要なインフラストラクチャコードを含む org.springframework.boot:spring-boot-test-autoconfigure を組み込みます。

Maven ビルドに Spring Boot スターターテストを含める
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>
Gradle ビルドに Spring Boot スターターテストを含める
dependencies {
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

@DataNeo4jTest は Spring Boot テストスライスです。テストスライスは、Neo4j を使用したテストに必要なすべてのインフラストラクチャ、つまりトランザクションマネージャー、クライアント、テンプレート、宣言されたリポジトリを、リアクティブな依存関係が存在するかどうかに応じて命令型またはリアクティブ型で提供します。テストスライスにはすでに @ExtendWith(SpringExtension.class) が含まれているため、JUnit 5 (JUnit Jupiter) で自動的に実行されます。

@DataNeo4jTest は、デフォルトで命令型インフラストラクチャとリアクティブインフラストラクチャの両方を提供し、暗黙的な @Transactional も追加します。ただし、Spring Test の @Transactional は、常に命令型トランザクションを意味します。宣言型トランザクションでは、命令型 PlatformTransactionManager とリアクティブ ReactiveTransactionManager のどちらが必要かを決定するメソッドの戻り値の型が必要です。

リアクティブリポジトリまたはサービスの正しいトランザクション動作をアサートするには、テストに TransactionalOperator を挿入するか、インフラストラクチャが正しいトランザクションマネージャーを選択できるようにする戻り値の型を公開するアノテーション付きメソッドを使用するサービスでドメインロジックをラップする必要があります。

テストスライスには、組み込みデータベースやその他の接続設定は組み込まれません。適切な接続を使用するかどうかはあなた次第です。

Neo4j テストコンテナーモジュール (英語) または Neo4j テストハーネスを使用する 2 つのオプションのいずれかを推奨します。Testcontainers はさまざまなサービス用のモジュールを備えた既知のプロジェクトですが、Neo4j テストハーネスはあまり知られていません。これは、Neo4j ベースの Java アプリケーションのテスト (英語) で説明されているストアドプロシージャをテストするときに特に役立つ埋め込みインスタンスです。ただし、テストハーネスはアプリケーションのテストにも使用できます。アプリケーションと同じ JVM 内でデータベースが起動されるため、パフォーマンスとタイミングが運用環境のセットアップと似ていない可能性があります。

便宜上、Neo4j テストハーネス 3.5 および 4.x/5.x および Testcontainers Neo4j の 3 つの可能なシナリオを提供します。3.5 および 4.x/5.x のバージョン間でテストハーネスが変更されたため、さまざまな例を提供します。また、4.0 には JDK 11 が必要です。

@DataNeo4jTest Neo4j テストハーネス 3.5 を使用する場合

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 および適切なリポジトリ構成で利用できます。

Neo4j 3.5 テストハーネスの使用
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 テストハーネスの使用を実行するには、次の依存関係が必要です。

Neo4j 4.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 および適切なリポジトリ構成で利用できます。

Neo4j 4.x/5.x テストハーネスの使用
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 とテストコンテナー Neo4j

もちろん、テストコンテナーの使用で示されているように、接続構成の原則は Testcontainers でも同じです。次の依存関係が必要です。

<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 の代替

上記のアノテーションがユースケースに適合しないシナリオがいくつかあります。そのうちの 1 つは、ドライバーの初期化方法を 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());
        }
    }
}