使用 SpringBootTest 和 Autowired 注解时 LogCaptor 捕获失败
LogCaptor fails to capture when using SpringBootTest and Autowired annotation
在我的集成测试中,当使用注解 SpringBootTest 以及自动装配服务时捕获日志时,我有一个奇怪的行为。我使用 LogCaptor 来捕获日志。
使用特定设置我无法捕获日志,我不明白为什么会这样。我不确定我是否错过了 spring 启动测试配置的某些内容,或者是否还有其他内容。
所以让我们假设有一个 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,例如拥有一个构造函数并使用自动装配对其进行注释,并将 logcaptor 设置放在那里,但它们确实有效,但我不明白为什么上述特定情况会失败。有人知道可能是什么问题吗?
在下面尝试,它应该有效
@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 测试 - 它通过了
失败测试中的问题是 class 变量 logcaptor
在创建测试应用程序上下文之前创建。在 bootstrapping 测试应用程序上下文期间,Spring Boot 将从头开始完全配置 logback。这会导致在 LogCaptor::forClass
中添加的 appender 丢失。
在成功的 @SpringBootTest
测试中,appender 是在上下文生命周期的后期添加的。
当您参加两个通过的 @SpringBootTest
测试中的任何一个并同时执行它们时,只要失败的测试没有先执行,您就可以使测试通过。那是因为 Spring 测试框架不会 bootstrap 一个新的上下文,而是重新使用第一个测试的上下文 运行。如果你用 @DirtiesContext(classMode = ClassMode.BEFORE_CLASS)
注释它的 class ,你可以让测试重新失败,因为在分配 class 变量 logcaptor
之后,将再次创建一个新的上下文。
恐怕对此无能为力。 Spring 引导团队对 github 的评论是关于一个类似的问题:https://github.com/spring-projects/spring-boot/issues/19323#issuecomment-563166919
在我的集成测试中,当使用注解 SpringBootTest 以及自动装配服务时捕获日志时,我有一个奇怪的行为。我使用 LogCaptor 来捕获日志。
使用特定设置我无法捕获日志,我不明白为什么会这样。我不确定我是否错过了 spring 启动测试配置的某些内容,或者是否还有其他内容。
所以让我们假设有一个 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,例如拥有一个构造函数并使用自动装配对其进行注释,并将 logcaptor 设置放在那里,但它们确实有效,但我不明白为什么上述特定情况会失败。有人知道可能是什么问题吗?
在下面尝试,它应该有效
@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 测试 - 它通过了
失败测试中的问题是 class 变量 logcaptor
在创建测试应用程序上下文之前创建。在 bootstrapping 测试应用程序上下文期间,Spring Boot 将从头开始完全配置 logback。这会导致在 LogCaptor::forClass
中添加的 appender 丢失。
在成功的 @SpringBootTest
测试中,appender 是在上下文生命周期的后期添加的。
当您参加两个通过的 @SpringBootTest
测试中的任何一个并同时执行它们时,只要失败的测试没有先执行,您就可以使测试通过。那是因为 Spring 测试框架不会 bootstrap 一个新的上下文,而是重新使用第一个测试的上下文 运行。如果你用 @DirtiesContext(classMode = ClassMode.BEFORE_CLASS)
注释它的 class ,你可以让测试重新失败,因为在分配 class 变量 logcaptor
之后,将再次创建一个新的上下文。
恐怕对此无能为力。 Spring 引导团队对 github 的评论是关于一个类似的问题:https://github.com/spring-projects/spring-boot/issues/19323#issuecomment-563166919