测试spring应用程序上下文无法启动的最佳方法是什么?

时间:2015-07-29 06:32:26

标签: spring junit spring-boot spring-test

我使用spring-boot-starter-web和spring-boot-starter-test。

假设我有一个用于绑定配置属性的类:

@ConfigurationProperties(prefix = "dummy")
public class DummyProperties {

    @URL
    private String url;

    // getter, setter ...

}

现在我想测试我的bean验证是否正确。如果未设置属性dummy.value或者包含无效的URL,则上下文应该无法启动(带有特定的错误消息)。如果属性包含有效的URL,则应启动上下文。 (测试将显示@NotNull缺失。)

测试类看起来像这样:

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = MyApplication.class)
@IntegrationTest({ "dummy.url=123:456" })
public class InvalidUrlTest {
    // my test code
}

此测试将失败,因为提供的属性无效。告诉Spring / JUnit最好的方法是什么:“是的,这个错误是预期的”。在普通的JUnit测试中,我会使用ExpectedException。

3 个答案:

答案 0 :(得分:6)

测试 Spring 应用程序上下文的最佳方法是使用 ApplicationContextRunner

在 Spring Boot 参考文档中有描述:
https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-developing-auto-configuration.html#boot-features-test-autoconfig

并且有一个关于它的快速指南:
https://www.baeldung.com/spring-boot-context-runner

示例用法

private static final String POSITIVE_CASE_CONFIG_FILE =  
"classpath:some/path/positive-case-config.yml";
private static final String NEGATIVE_CASE_CONFIG_FILE =  
"classpath:some/path/negative-case-config.yml";

@Test
void positiveTest() {
  ApplicationContextRunner contextRunner = new ApplicationContextRunner()
    .withInitializer(new ConfigDataApplicationContextInitializer())//1
    .withInitializer(new ConditionEvaluationReportLoggingListener(LogLevel.DEBUG))//2
    .withUserConfiguration(MockBeansTestConfiguration.class)//3
    .withPropertyValues("spring.config.location=" + POSITIVE_CASE_CONFIG_FILE)//4
    .withConfiguration(AutoConfigurations.of(BookService.class));//5
  contextRunner
    .run((context) -> {
      Assertions.assertThat(context).hasNotFailed();//6
    });
}

@Test
void negativeTest() {
  ApplicationContextRunner contextRunner = new ApplicationContextRunner()
    .withInitializer(new ConfigDataApplicationContextInitializer())//1
    .withInitializer(new ConditionEvaluationReportLoggingListener(LogLevel.DEBUG))//2
    .withUserConfiguration(MockBeansTestConfiguration.class)//3
    .withPropertyValues("spring.config.location=" + NEGATIVE_CASE_CONFIG_FILE)//4
    .withConfiguration(AutoConfigurations.of(BookService.class));//5
  contextRunner
    .run((context) -> {
      assertThat(context)
        .hasFailed();
      assertThat(context.getStartupFailure())
        .isNotNull();
      assertThat(context.getStartupFailure().getMessage())
        .contains("Some exception message");
      assertThat(extractFailureCauseMessages(context))
        .contains("Cause exception message");
    });
}

private List<String> extractFailureCauseMessages(AssertableApplicationContext context) {
  var failureCauseMessages = new ArrayList<String>();
  var currentCause = context.getStartupFailure().getCause();
  while (!Objects.isNull(currentCause)) {//7
    failureCauseMessages.add(currentCause.getMessage());
    currentCause = currentCause.getCause();
  }
  return failureCauseMessages;
}

Junit5 Spring Boot Test Annotations 中类似定义的示例说明:

  1. 触发加载 application.propertiesapplication.yml 等配置文件
  2. 当应用程序上下文失败时使用给定的日志级别记录 ConditionEvaluationReport
  3. 提供指定模拟bean的类,即。我们在 @Autowired BookRepository 中有 BookService,我们在 BookRepository 中提供模拟 MockBeansTestConfiguration。类似于测试类中的 @Import({MockBeansTestConfiguration.class}) 和普通 Junit5 Spring Boot 测试中带有模拟 bean 的类中的 @TestConfiguration
  4. 相当于 @TestPropertySource(properties = { "spring.config.location=" + POSITIVE_CASE_CONFIG_FILE})
  5. 触发给定类的 spring 自动配置,不是直接等效的,但它类似于在正常测试中使用 @ContextConfiguration(classes = {BookService.class})@SpringBootTest(classes = {BookService.class})@Import({BookService.class}) 一起
  6. AssertJ 库中的 Assertions.class,Assertions.assertThat 应该有静态导入,但我想显示此方法的来源
  7. 应该有 Objects.isNull 的静态导入,但我想显示此方法的来源

MockBeansTestConfiguration 类:

@TestConfiguration
public class MockBeansTestConfiguration {
  private static final Book SAMPLE_BOOK = Book.of(1L, "Stanisław Lem", "Solaris", "978-3-16-148410-0");

  @Bean
  public BookRepository mockBookRepository() {
    var bookRepository = Mockito.mock(BookRepository.class);//1
    Mockito.when(bookRepository.findByIsbn(SAMPLE_BOOK.getIsbn()))//2
           .thenReturn(SAMPLE_BOOK);
    return bookRepository;
  }
}

备注:
1,2。应该有静态导入,但我想展示这个方法的来源

答案 1 :(得分:2)

为什么要开始集成测试?你为什么要为它开始一个完整的Spring Boot应用程序?

这对我来说就像是单元测试。话虽这么说,你有几个选择:

  • 不要添加@IntegrationTest,Spring Boot不会启动Web服务器(使用@PropertySource将值传递给您的测试,但将无效值传递给您的错误感觉不对整个考试类)
  • 您可以使用spring.main.web-environment=false来停用网络服务器(但鉴于上述内容,这很愚蠢)
  • 编写一个处理您DummyProperties的单元测试。您甚至不需要为此启动Spring Boot应用程序。看our own test suite

我绝对会选择最后一个。也许你有充分的理由对它进行集成测试?

答案 2 :(得分:0)

我认为最简单的方法是:

public class InvalidUrlTest {

    @Rule
    public DisableOnDebug testTimeout = new DisableOnDebug(new Timeout(5, TimeUnit.SECONDS));
    @Rule
    public ExpectedException expected = ExpectedException.none();

    @Test
    public void shouldFailOnStartIfUrlInvalid() {
        // configure ExpectedException
        expected.expect(...

        MyApplication.main("--dummy.url=123:456");
    }

// other cases
}