Spring 引导 - 如何从多模块项目中的姐妹模块启动我的 Spring 引导应用程序?

Spring Boot - How to start my Spring Boot Application from sister module in a multi-module project?

我有一个包含两个项目的多模块项目:backendclient。后端是正常的 Spring Boot Rest API,没什么特别的。 client 模块只是一个使用 Rest API 的 Java 库。

后端有"war"的封装作为后端,因为它也使用了JSP,需要部署到一个servlet容器中。使用@SpringBootTest.

仍然可以轻松测试后端

现在我想在 client 模块中使用后端模块作为沙箱服务器进行一些集成测试。

要使用我添加的客户端模块中的所有后端 classes

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-war-plugin</artifactId>
  <configuration>
    <attachClasses>true</attachClasses>
  </configuration>
</plugin>

并使用 classes

将后端配置为客户端中的测试依赖项

在我的 client/src/test/java 中,我有一个助手 class 可以启动后端模块

@Configuration  
public class SandboxServer {

  @Bean
  public ConfigurableApplicationContext backend() {
    return 
      new SpringApplicationBuilder(BackendApplication.class)
      .sources(SandboxServerConfig.class)
      .run("spring.profiles.active=sandbox")
  }
}

配置文件"sandbox"用于设置测试数据库等。但我遇到了更多问题。第一个问题是关于文档根目录,所以我配置它:

public class SandboxServerConfig 
  implements WebServerFactoryCustomizer<TomcatServletWebServerFactory> {
  @Override
  public void customize(TomcatServletWebServerFactory factory) {
    factory.setDocumentRoot(new File("../backend/src/main/webapp"));
  }
}

但它仍然不起作用,因为 Spring 没有获取 backend/src/main/resources/application.properties

这可能是正确的,因为它不在客户端模块的根 class 路径中。

所以它并没有真正起作用。我想不可能只在集成测试中启动兄弟模块。

如何实现启动兄弟spring引导模块进行集成测试?像这样的场景的最佳实践是什么?

您可以像这样使用 TestPropertySource 覆盖 application.properties 位置:

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = BlaApplication.class)
@TestPropertySource(locations="/path/to/backend/src/main/resources/application.properties")
public class ExampleApplicationTests {

}

我找到了一个更可靠的解决方案。在我的同级项目 "frontend" 中,我有一个组件以集成模式启动后端服务器当且仅当它尚未 运行ning.

好处:

  • 实测WAR
  • 您可以在 IDE 之前开始 WAR 并让测试 运行 快速
  • 如果你 运行 它与 maven 一起,它在所有测试之前只启动一次
  • 无需构建配置(如在 maven 中预集成)
  • 进程与 Junit 运行时间分开,因此无需复杂设置。

缺点:

  • 您需要先构建包,然后才能 运行 在前端进行任何集成测试。但是,嘿,你应该在测试之前构建你的包。这就是集成测试的意义所在。

这是我的 SandboxServerProcess.class。

import org.springframework.stereotype.Component;   
import javax.annotation.*;
import javax.annotation.*;
import java.io.*;
import java.net.*;
import java.util.*;

@Component
@Profile("integration")
public class SandboxServerProcess {
    private static final String WAR = "../backend/target/backend.war";
    private final static int PORT = 8081;
    private boolean startedByMe;

    @PostConstruct
    public void start() throws Exception {
        if (isStarted()) {
            return;
        }
        testWarExists();
        packagedWar("start");
        if (waitForStartup()) {
            startedByMe = true;
            return;
        }
        throw new RuntimeException("Sandbox Server not started");
    }

    private void testWarExists() {
        File file = new File(WAR);
        if (!file.exists()) {
            throw new RuntimeException("WAR does not exist:" + file.getAbsolutePath());
        }
    }

    @PreDestroy
    public void stop() throws IOException {
        if (startedByMe) {
            packagedWar("stop");
        }
    }

    private void packagedWar(String command) throws IOException {
        ProcessBuilder builder = new ProcessBuilder();
        builder.environment().put("MODE", "service");
        builder.environment().put("SPRING_PROFILES_ACTIVE", "integration");
        builder.environment().put("APP_NAME", "backend");
        builder.environment().put("PID_FOLDER", "./");
        builder.environment().put("LOG_FOLDER", "./");
        List<String> commands = new ArrayList<>();
        commands.add(WAR);
        commands.add(command);
        builder.command(commands);
        builder.inheritIO();
        builder.redirectErrorStream(true);
        builder.start();
    }

    private boolean isStarted() {
        try {
            Socket socket = new Socket();
            InetSocketAddress sa = new InetSocketAddress("localhost", PORT);
            socket.connect(sa, 500);
            logger.warn("SandboxServer is started");
            return true;
        } catch (IOException e) {
            return false;
        }
    }

    private boolean waitForStartup() throws InterruptedException {
        for (int i = 1; i < 30; i++) {
            if (isStarted()) {
                return true;
            }
            logger.warn("SandboxServer not yet ready, tries: " + i);
            Thread.sleep(1000);
        }
        return false;
    }
}