如何在一次测试中测试多个Spring Boot应用程序?

我有一个多模块Maven项目,其中包含2个Spring Boot应用程序

父母

  • fooApp
  • 酒吧应用程序
  • 测试

如何设置一个测试,您可以在其中加载单独的弹簧引导应用程序,每个应用程序都有自己的配置上下文,在同一过程中。

public abstract class AbstractIntegrationTest {//test module

    protected FOO foo;
    protected BAR bar;

    @RunWith(SpringJUnit4ClassRunner.class)
    @WebAppConfiguration
    @IntegrationTest
    @Transactional
    @SpringApplicationConfiguration(classes = foo.Application.class)
    public class FOO {
        public MockMvc mockMvc;

        @Autowired
        public WebApplicationContext wac;

        @Before
        public void _0_setup() {
            MockitoAnnotations.initMocks(this);
            mockMvc = MockMvcBuilders.webAppContextSetup(wac).build();
            TestCase.assertNotNull(mockMvc);
        }

        public void login(String username) {
        }
    }

    @RunWith(SpringJUnit4ClassRunner.class)
    @WebAppConfiguration
    @IntegrationTest
    @Transactional
    @SpringApplicationConfiguration(classes = bar.Application.class)
    public class BAR {

        @Autowired
        public WebApplicationContext wac;

        public MockMvc restMvc;

        @Before
        public void _0_setup() {
            MockitoAnnotations.initMocks(this);
            restMvc = MockMvcBuilders.webAppContextSetup(wac).build();
            TestCase.assertNotNull(restMvc);
        }

        public void login(String username) {
        }
    }

    @Before
    public void _0_setup() {
        foo = new FOO();
        bar = new BAR();
    }
}

集成测试示例

public class IntegrationTest extends AbstractIntegrationTest {

    @Test
    public void login() {
        foo.login("foologin");
        bar.login("barlogin");
    }

}

答案 1

我同意@rainerhahnekamp,他说你试图实现的更像是一个系统/集成测试。

但是,如果您仍然想以这种方式进行测试,我认为这仍然是可行的。

首先,需要了解的一件重要事情是:
在项目内导入和项目将使两个项目的配置文件可供类加载器使用,并产生不可预测的结果。示例 :将仅加载两个文件中的一个。因此,您将必须使用2个不同的配置文件来加载2个单独的配置文件设置。
出于与项目文件重叠的相同原因,由一个应用程序在另一个应用程序可见的包中定义的 Bean 将在两个应用程序上下文中加载。fooAppbarApptestapplication.properties

为了测试这个概念,我在每个项目中创建了一个服务和一个休息控制器,每个控制器都有一个“分析”属性文件:

酒吧应用程序


@EnableAutoConfiguration(
    exclude = {SecurityAutoConfiguration.class,
    ManagementWebSecurityAutoConfiguration.class})
@SpringBootApplication
public class BarApp {


  public static void main(String[] args) {
    SpringApplication.run(BarApp.class, args);
  }

}

@Service
public class BarService {

  public String yield() {
    return "BarService !";
  }

}

@RestController
public class BarResource {

  private final BarService barService;

  public BarResource(BarService barService) {
    this.barService = barService;
  }

  @GetMapping("/bar")
  public String getBar() {
    return barService.yield();
  }

}

应用程序栏属性 :

server.port=8181

fooApp


@EnableConfigurationProperties
@SpringBootApplication
public class FooApp {
  
  public static void main(String[] args) {
    SpringApplication.run(FooApp.class, args);
  }

}

@Service
public class FooService {

  public String yield() {
    return "FooService !";
  }

}

@RestController
public class FooResource {

  private final FooService fooService;

  public FooResource(FooService fooService) {
    this.fooService = fooService;
  }

  @GetMapping("/foo")
  public String getFoo() {
    return fooService.yield();
  }

}

application-foo.properties :

server.port=8282

测试

class TestApps {

  @Test
  void TestApps() {
    // starting and customizing BarApp
    {
      SpringApplication barApp = new SpringApplication(BarApp.class);
      barApp.setAdditionalProfiles("bar"); // to load 'application-bar.properties'
      GenericWebApplicationContext barAppContext = (GenericWebApplicationContext) barApp.run();

      BarService barServiceMock = Mockito.mock(BarService.class);
      Mockito.doReturn("mockified bar !").when(barServiceMock).yield();
      barAppContext.removeBeanDefinition("barService");
      barAppContext.registerBean("barService", BarService.class, () -> barServiceMock);
    }

    // starting and customizing FooApp
    {
      SpringApplication fooApp = new SpringApplication(FooApp.class);
      fooApp.setAdditionalProfiles("foo"); // to load 'application-foo.properties'
      GenericWebApplicationContext fooAppContext = (GenericWebApplicationContext) fooApp.run();

      FooService fooServiceMock = Mockito.mock(FooService.class);
      Mockito.doReturn("mockified foo !").when(fooServiceMock).yield();
      fooAppContext.removeBeanDefinition("fooService");
      fooAppContext.registerBean("fooService", FooService.class, () -> fooServiceMock);
    }

    RestTemplate restTemplate = new RestTemplate();
    String barResourceUrl = "http://localhost:8181/bar";
    ResponseEntity<String> barResponse = restTemplate.getForEntity(barResourceUrl, String.class);

    String fooResourceUrl = "http://localhost:8282/foo";
    ResponseEntity<String> fooResponse = restTemplate.getForEntity(fooResourceUrl, String.class);

    System.out.println(barResponse.getBody());
    System.out.println(fooResponse.getBody());
  }

}

启动测试会产生:

mockified bar !
mockified foo !

顺便说一句,我怀疑你的项目会像我的例子一样简单,我怀疑你会遇到与我之前强调的重要事情有关的问题。


答案 2

给定两个包 com.foo.module1 和 com.foo.module2,你必须为每个包创建一个配置类。例如,对于模块 1:

@SpringBootApplication public class Config1 {}

如果你想通过使用单个包的Spring Bean来运行应用程序,你可以通过使用SpringApplicationBuilder来做到这一点。工作代码段:

   new SpringApplicationBuilder(com.foo.module1.Config1.class)
     .showBanner(false)
     .run()

这将启动Spring与Config1,它只搜索(@ComponentScan包含在@SpringBootApplication)在其软件包中寻找豆子。

如果你想运行完整的应用程序,例如,一次运行所有两个模块,你必须在上面的pack包com.foo中创建一个配置类。

在下面提到的情况下,由于像spring-boot-starter这样的库,在单个应用程序中运行两个模块可能会以不希望的方式相互干扰,我只能想到两种可能性:

  1. 使用OSGi:这可能无法完全解决问题,并且可能是一个非常复杂的设置或
  2. 将应用程序拆分为两个应用程序并创建接口。Spring Boot 也是微服务架构的不错选择。

推荐