使用 SpringBootTest 和 Autowired 注解时 LogCaptor 无法捕获
Posted
技术标签:
【中文标题】使用 SpringBootTest 和 Autowired 注解时 LogCaptor 无法捕获【英文标题】:LogCaptor fails to capture when using SpringBootTest and Autowired annotation 【发布时间】:2022-01-12 11:06:49 【问题描述】:在使用注释 SpringBootTest 和自动装配服务时,我在捕获日志的集成测试中有一个奇怪的行为。我使用LogCaptor 来捕获日志。
通过特定设置,我无法捕获日志,而且我无法理解为什么会发生这种情况。我不确定我是否错过了 Spring Boot 测试配置中的某些内容,或者是否是其他内容。
所以我们假设有一个 FooService:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
@Service
public class FooService
private static final Logger LOGGER = LoggerFactory.getLogger(FooService.class);
public String hello()
LOGGER.error("Expected error message");
return "hello";
以及集成测试:
这个失败了
import nl.altindag.log.LogCaptor;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import static org.assertj.core.api.Assertions.assertThat;
@SpringBootTest
public class AppIT
private static final LogCaptor logcaptor = LogCaptor.forClass(FooService.class);
@Autowired
private FooService fooService;
@Test
void testMethod()
fooService.hello();
assertThat(logcaptor.getErrorLogs()).containsExactly("Expected error message");
上面的测试会失败,但是当我不使用 SpringBootTest 注释并且只是在测试方法中初始化 FooService 时它通过了:
这些都是通过
import nl.altindag.log.LogCaptor;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
public class AppIT
private static final LogCaptor logcaptor = LogCaptor.forClass(FooService.class);
@Test
void testMethod()
new FooService().hello();
assertThat(logcaptor.getErrorLogs()).containsExactly("Expected error message");
import nl.altindag.log.LogCaptor;
import nl.altindag.server.service.FooService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import javax.annotation.PostConstruct;
import static org.assertj.core.api.Assertions.assertThat;
@SpringBootTest
public class AppIT
private FooService fooService;
private LogCaptor logcaptor;
@Autowired
AppIT(FooService fooService)
this.fooService = fooService;
@PostConstruct
void init()
this.logcaptor = LogCaptor.forClass(FooService.class);
@Test
void testMethod()
fooService.hello();
assertThat(logcaptor.getErrorLogs()).containsExactly("Expected error message");
import nl.altindag.log.LogCaptor;
import nl.altindag.server.service.FooService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import static org.assertj.core.api.Assertions.assertThat;
@SpringBootTest
public class AppIT
private FooService fooService;
private LogCaptor logcaptor;
@Autowired
AppIT(FooService fooService)
this.fooService = fooService;
this.logcaptor = LogCaptor.forClass(FooService.class);
@Test
void testMethod()
fooService.hello();
assertThat(logcaptor.getErrorLogs()).containsExactly("Expected error message");
已使用以下库:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.6.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot</artifactId>
<version>2.6.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>2.6.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.8.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.8.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>3.21.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.github.hakky54</groupId>
<artifactId>logcaptor</artifactId>
<version>2.7.2</version>
<scope>test</scope>
</dependency>
我曾尝试在测试和其他设置中使用 postconstruct,例如拥有一个构造函数并使用 autowired 对其进行注释并将 logcaptor 设置放在那里确实有效,但我不明白为什么上述具体情况会失败。有人知道可能是什么问题吗?
【问题讨论】:
【参考方案1】:失败测试的问题是类变量logcaptor
是在创建测试应用上下文之前创建的。在测试应用上下文的引导过程中,Spring Boot 会完全从头开始配置 logback。这会导致 LogCaptor::forClass
中添加的 appender 丢失。
在成功的@SpringBootTest
测试中,附加程序是在上下文生命周期的后期添加的。
只要不首先执行失败的测试,您就可以通过两个通过@SpringBootTest
测试中的任何一个并一起执行它们,从而使测试通过。这是因为 Spring 测试框架不会引导新的上下文,而是重用第一个运行的测试的上下文。如果你用@DirtiesContext(classMode = ClassMode.BEFORE_CLASS)
注释它的类,你可以让测试恢复失败,因为在分配类变量logcaptor
之后将再次创建一个新的上下文。
恐怕对此无能为力。 Spring Boot 团队在 github 上的这条评论是关于类似的问题:https://github.com/spring-projects/spring-boot/issues/19323#issuecomment-563166919
【讨论】:
很好的答案,我同意你的看法。【参考方案2】:试试下面,它应该可以工作
@SpringBootTest
public class AppIT
private static LogCaptor logcaptor = LogCaptor.forClass(FooService.class);
@Autowired
private FooService fooService;
@BeforeEach
void beforeEach()
logcaptor = LogCaptor.forClass(FooService.class);
logcaptor.clearLogs();
@Test
void testMethod()
fooService.hello();
assertThat(logcaptor.getErrorLogs()).containsExactly("Expected error message");
在 eclipse 上运行测试 - 它通过了
【讨论】:
感谢您的回答,但我很好奇为什么我的示例中的特定设置失败了,所以这是我的主要问题以上是关于使用 SpringBootTest 和 Autowired 注解时 LogCaptor 无法捕获的主要内容,如果未能解决你的问题,请参考以下文章
如何使用@SpringBootTest 验证作业是不是运行了另一个作业