简体   繁体   English

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

[英]How to test multiple Spring Boot applications in one test?

I have a multi-module Maven project with 2 Spring Boot applications我有一个带有 2 个 Spring 引导应用程序的多模块 Maven 项目

parent父母

  • fooApp fooApp
  • barApp酒吧应用
  • test测试

How to set up a test where you can load separate spring boot applications, each with its own configuration context, in the same process.如何设置测试,您可以在同一过程中加载单独的 spring 引导应用程序,每个应用程序都有自己的配置上下文。

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

And an example of an integration test和一个集成测试的例子

public class IntegrationTest extends AbstractIntegrationTest {

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

}

I agree with @rainerhahnekamp who said that what you are trying to achieve is more like a system/integration test.我同意@rainerhahnekamp 的观点,他说您想要实现的更像是系统/集成测试。

However if you still want to do your test this way, I think it's still doable.但是,如果您仍然想以这种方式进行测试,我认为它仍然是可行的。

First, one important things to know :首先,要知道一件重要的事情
Importing both fooApp and barApp project inside test project will make configuration files of both projects available to the classloader, and will produce impredictable results.test项目中同时导入fooAppbarApp项目将使两个项目的配置文件都可用于类加载器,并且会产生不可预测的结果。 Exemple : only one of the two application.properties file will be loaded.示例:将仅加载两个application.properties文件之一。 So you will have to use 2 differents profiles to load 2 separated configuration files setup.因此,您必须使用 2 个不同的配置文件来加载 2 个单独的配置文件设置。
For the same reason of projects files overlapping, beans defined by one application in packages visible by the other one will be loaded in both apps context.由于项目文件重叠的相同原因,一个应用程序在另一个应用程序可见的包中定义的 bean 将在两个应用程序上下文中加载。

To test the concept I created one service and one rest controller in each project, each with a 'profiled' property file :为了测试这个概念,我在每个项目中创建了一个服务和一个休息控制器,每个都有一个“配置文件”属性文件:

barApp酒吧应用


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

}

application-bar.properties :应用-bar.properties:

server.port=8181

fooApp 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 :应用程序-foo.properties:

server.port=8282

test测试

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

}

Launching the test produces :启动测试会产生:

mockified bar !
mockified foo !

By the way I doubt your projects will be as simple as my example and I suspect you will run into issues related to important things I highlighted earlier.顺便说一句,我怀疑您的项目会像我的示例一样简单,并且我怀疑您会遇到与我之前强调的重要事项相关的问题。

YOU need to configure @ContextConfiguration to point both apps您需要配置 @ContextConfiguration 以指向两个应用程序

Given two packages com.foo.module1, and com.foo.module2 you have to create a Configuration class per package.给定两个包 com.foo.module1 和 com.foo.module2,您必须为每个包创建一个配置类。 For example for module1:例如对于模块 1:

@SpringBootApplication public class Config1 {}

If you want to run the application by using only Spring beans of a single package you can do that by using the SpringApplicationBuilder.如果您只想使用单个包的 Spring bean 来运行应用程序,您可以使用 SpringApplicationBuilder 来实现。 A working snippet:一个工作片段:

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

That would boot up Spring with Config1, which only searches (@ComponentScan is included in @SpringBootApplication) in its package for beans.这将使用 Config1 启动 Spring,它仅在其包中搜索(@ComponentScan 包含在 @SpringBootApplication 中)bean。

If you have want to run the complete application, eg all two modules at once, you'de have to create a configuration class in the upper packages com.foo.如果您想运行完整的应用程序,例如同时运行所有两个模块,您必须在上层包 com.foo 中创建一个配置类。

In the case that was mentioned below, where running the two modules within a single application might probably interfere with each other in an undesired way due to libraries like the spring-boot-starters, I can only think of two possibilities:在下面提到的情况下,由于 spring-boot-starters 之类的库,在单个应用程序中运行两个模块可能会以不希望的方式相互干扰,我只能想到两种可能性:

  1. Using OSGi: Which might not solve the issue completely and might turn out be quite a complex setup or使用 OSGi:这可能无法完全解决问题,而且可能是一个相当复杂的设置或
  2. Splitting the application into two applications and creating interfaces.将应用程序拆分为两个应用程序并创建接口。 Spring Boot is also a good choice for a Microservice architecture. Spring Boot 也是微服务架构的不错选择。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM