With Spring Boot and @DataNeo4jTest

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

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 同时提供命令式和响应式设施,还添加了一个隐式的 @Transactional。但是,Spring 测试中的 @Transactional 始终表示命令式的事务,因为声明式事务需要通过方法的返回类型来确定是需要命令式的 PlatformTransactionManager 还是响应式的 ReactiveTransactionManager。 为了断言响应式资源库或服务的正确事务行为,您需要将 TransactionalOperator 注入到测试中,或将您的域逻辑包装在使用带注释方法的服务中,方法返回类型使得该基础设施可以选择正确的事务管理器。 测试切片不会引入嵌入式数据库或任何其他连接设置。使用适当的连接由您决定。 我们建议两个选项之一:使用 Neo4j Testcontainers module或 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。

@DataNeo4jTest with Neo4j test harness 3.5

您需要以下依赖项来运行 [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 和适当的资源库配置下获得。

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 创建嵌入式 Neo4j 的入口点
2 这是一个 Spring Boot 注解,允许动态注册的应用程序属性。我们覆盖相应的 Neo4j 设置。
3 在所有测试后关闭 Neo4j。

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

您需要以下依赖项来运行 [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 和适当的资源库配置下获得。

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 创建嵌入式 Neo4j 的入口点
2 关闭无用的 Neo4j HTTP 服务器
3 这是一个 Spring Boot 注解,允许动态注册的应用程序属性。我们覆盖相应的 Neo4j 设置。
4 在所有测试后关闭 Neo4j。

@DataNeo4jTest with Testcontainers Neo4j

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

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

以及一个完整的测试:

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% 地控制驱动程序的初始化方式。通过运行测试容器,您可以使用类似这样的嵌套静态配置类来完成此操作:

@TestConfiguration(proxyBeanMethods = false)
static class TestNeo4jConfig {

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

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

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