[英]How to test @Configuration and @Bean in a Spring Boot Application?
[英]How to test a component / bean in Spring Boot
为了测试在Spring启动应用程序的组件/豆, 春天启动文档的测试部分提供了很多的信息和多种方式: @Test
, @SpringBootTest
, @WebMvcTest
, @DataJpaTest
仍然很多其他方式。
为什么提供这么多方式? 如何决定青睐的方式?
我是否应该将使用 Spring Boot 测试注释(例如@SpringBootTest
、 @WebMvcTest
、 @DataJpaTest
注释的测试类视为集成测试?
PS:我创建这个问题是因为我注意到许多开发人员(甚至有经验的)没有得到使用注释而不是另一个注释的后果。
为组件编写简单的单元测试,您可以在不加载 Spring 容器的情况下直接测试(在本地和 CI 构建中运行它们)。
为不加载 Spring 容器就无法直接测试的组件编写部分集成测试/ 切片单元测试,例如与 JPA、控制器、REST 客户端、JDBC 相关的组件......(在本地和 CI 构建中运行它们)
为一些带来价值的高级组件编写一些完整的集成测试(端到端测试)(在 CI 构建中运行它们)。
在 Spring 的通用方式中,任何组件都可以在集成测试中进行测试,并且只有某些类型的组件适合进行整体测试(没有容器)。
但请注意,无论有没有 spring,unitary 和 integration 测试都不是对立的,而是互补的。
您认识到要测试的代码没有来自 Spring 容器的任何依赖项,因为组件/方法不使用 Spring 功能来执行其逻辑。
拿那个FooService
类:
@Service
public class FooService{
private FooRepository fooRepository;
public FooService(FooRepository fooRepository){
this.fooRepository = fooRepository;
}
public long compute(...){
List<Foo> foos = fooRepository.findAll(...);
// core logic
long result =
foos.stream()
.map(Foo::getValue)
.filter(v->...)
.count();
return result;
}
}
FooService
执行一些不需要 Spring 执行的计算和逻辑。
实际上,无论有没有容器, compute()
方法都包含我们想要断言的核心逻辑。
相反,您将难以在没有 Spring 的情况下测试FooRepository
,因为 Spring Boot 会为您配置数据源、JPA 上下文,并检测您的FooRepository
接口以向其提供默认实现和其他多项内容。
测试控制器(rest 或 MVC)也是如此。
如果没有 Spring,控制器如何绑定到端点? 控制器如何在没有 Spring 的情况下解析 HTTP 请求并生成 HTTP 响应? 它根本无法做到。
在您的应用程序中使用 Spring Boot 并不意味着您需要为您运行的任何测试类加载 Spring 容器。
当您编写不需要来自 Spring 容器的任何依赖项的测试时,您不必在测试类中使用/加载 Spring。
您将自己实例化要测试的类,而不是使用 Spring,并在需要时使用模拟库将被测实例与其依赖项隔离。
这是遵循的方法,因为它速度快并且有利于测试组件的隔离。
这里如何对上面介绍的FooService
类进行单元测试。
您只需要模拟FooRepository
即可测试FooService
的逻辑。
使用 JUnit 5 和 Mockito,测试类可能如下所示:
import org.mockito.junit.jupiter.MockitoExtension;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
@ExtendWith(MockitoExtension.class)
class FooServiceTest{
FooService fooService;
@Mock
FooRepository fooRepository;
@BeforeEach
void init{
fooService = new FooService(fooRepository);
}
@Test
void compute(){
List<Foo> fooData = ...;
Mockito.when(fooRepository.findAll(...))
.thenReturn(fooData);
long actualResult = fooService.compute(...);
long expectedResult = ...;
Assertions.assertEquals(expectedResult, actualResult);
}
}
编写端到端测试需要加载一个容器,其中包含应用程序的整个配置和 bean。
实现@SpringBootTest
的方法是:
注释的工作原理是通过 SpringApplication 创建在测试中使用的 ApplicationContext
您可以通过这种方式使用它来测试它而无需任何模拟:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.junit.jupiter.api.Test;
@SpringBootTest
public class FooTest {
@Autowired
Foo foo;
@Test
public void doThat(){
FooBar fooBar = foo.doThat(...);
// assertion...
}
}
但是,如果有意义,您也可以模拟容器的一些 bean:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.boot.test.context.SpringBootTest;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
@SpringBootTest
public class FooTest {
@Autowired
Foo foo;
@MockBean
private Bar barDep;
@Test
public void doThat(){
Mockito.when(barDep.doThis()).thenReturn(...);
FooBar fooBar = foo.doThat(...);
// assertion...
}
}
请注意模拟的区别,因为您要模拟Bar
类的普通实例( org.mockito.Mock
注释)和要模拟 Spring 上下文的Bar
bean( org.springframework.boot.test.mock.mockito.MockBean
注释)。
加载完整的 spring 上下文需要时间。 所以你应该谨慎使用@SpringBootTest
因为这可能会使单元测试执行时间很长,而且通常你不希望强烈减慢开发人员机器上的本地构建和测试反馈,这对于使测试编写愉快和重要对开发人员有效。
这就是为什么“慢”测试通常不在开发人员的机器上执行的原因。
因此,您应该使它们成为集成测试(在测试类的命名中使用IT
后缀而不是Test
后缀)并确保这些仅在持续集成构建中执行。
但是由于 Spring Boot 作用于应用程序中的许多事物(rest 控制器、MVC 控制器、JSON 序列化/反序列化、持久性等等...),您可以编写许多仅在 CI 构建上执行的单元测试,而这不是也可以。
仅在 CI 构建上执行端到端测试是可以的,但仅在 CI 构建上执行持久性、控制器或 JSON 测试则根本不行。
事实上,开发人员构建会很快,但作为缺点,在本地执行的测试只会检测到可能回归的一小部分......
为了防止这种警告,Spring Boot 提供了一种中间方式:部分集成测试或切片测试(他们称之为):下一点。
正如“识别可以进行简单测试(没有弹簧)的测试”这一点中所解释的那样,某些组件只能使用正在运行的容器进行测试。
但是为什么使用@SpringBootTest
加载应用程序的所有 bean 和配置,而您只需要加载几个特定的配置类和 bean 来测试这些组件呢?
例如,为什么要加载完整的 Spring JPA 上下文(bean、配置、内存数据库等)来测试控制器部分?
反过来为什么要加载与 Spring 控制器关联的所有配置和 bean 来测试 JPA 存储库部分?
Spring Boot 使用切片测试功能解决了这一点。
这些不如普通单元测试(即没有容器)快,但它们确实比加载整个 spring 上下文快得多。 所以在本地机器上执行它们通常是可以接受的。
每个切片测试风格都会加载一组非常有限的自动配置类,您可以根据需要进行修改。
一些常见的切片测试功能:
要测试该对象 JSON 序列化和反序列化是否按预期工作,您可以使用 @JsonTest 批注。
要测试 Spring MVC 控制器是否按预期工作,请使用
@WebMvcTest
注释。
要测试 Spring WebFlux 控制器是否按预期工作,您可以使用
@WebFluxTest
注释。
您可以使用
@DataJpaTest
批注来测试 JPA 应用程序。
您还有许多 Spring Boot 提供给您的其他切片口味。
请参阅文档的测试部分以获取更多详细信息。
请注意,如果您需要定义一组特定的 bean 来加载内置测试切片注释未解决的问题,您还可以创建自己的测试切片注释( https://spring.io/blog/2016/08 /30/custom-test-slice-with-spring-boot-1-4 )。
几天前,我遇到了一个案例,我会在部分集成中测试一个依赖于几个 bean 的服务 bean,而这些 bean 本身也依赖于其他 bean。 我的问题是,由于通常的原因(http 请求和数据库中包含大量数据的查询),必须模拟两个深度依赖 bean。
加载所有 Spring Boot 上下文看起来开销很大,所以我尝试只加载特定的 bean。 为了实现这一点,我使用@SpringBootTest
注释测试类,并指定classes
属性来定义要加载的配置/beans 类。
经过多次尝试,我得到了一些似乎有效的东西,但我必须定义要包含的重要 bean/配置列表。
那真的不整洁也不可维护。
因此,作为更清晰的选择,我选择使用 Spring Boot 2.2 提供的惰性 bean 初始化功能:
@SpringBootTest(properties="spring.main.lazy-initialization=true")
public class MyServiceTest { ...}
这样做的好处是只加载运行时使用的 bean。
我完全不认为使用该属性必须成为测试类中的规范,但在某些特定的测试用例中,这似乎是正确的方式。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.