如何在Spring Boot中测试组件/ bean

要在Spring Boot应用程序中测试组件/ bean, Spring Boot文档的测试部分提供了大量信息和多种方式: @SpringBootTest @WebMvcTest@DataJpaTest @SpringBootTest@WebMvcTest@DataJpaTest以及许多其他方法。
为什么提供这么多方法? 如何决定赞成的方式?
我应该考虑作为集成测试我的测试类使用Spring Boot测试注释注释,例如@SpringBootTest@WebMvcTest@DataJpaTest

PS:我创建了这个问题,因为我注意到许多开发人员(甚至经验丰富的人)都没有得到使用注释而不是另一个注释的后果。

TL-DR

  • 可以直接测试的组件编写普通unit testing, 而无需加载Spring容器 (在本地和CI构建中运行它们)。
  • 不能直接测试的组件编写部分集成测试/ 切片unit testing , 而不加载Spring容器,例如与JPA,控制器,REST客户端,JDBC相关的组件……(在本地和CI构建中运行它们)

  • 为一些带有值的高级组件编写一些完整的集成测试(端到端测试)(在CI构建中运行它们)。


测试组件的3种主要方法

  • 普通unit testing(不加载Spring容器)
  • 完全集成测试(使用所有配置和bean加载Spring容器)
  • 部分集成测试/测试切片(加载具有非常有限的配置和bean的Spring容器)

是否可以通过以下三种方式测试所有组件?

在使用Spring的一般方法中,任何组件都可以在集成测试中进行测试,并且只有某些组件适合unit testing(没有容器)。
但请注意,无论是否有弹簧,单一和集成测试都不是对立的,而是互补的。

写一个简单的unit testing

在应用程序中使用Spring Boot并不意味着您需要为运行的任何测试类加载Spring容器。
当您编写一个不需要Spring容器的任何依赖项的测试时, 您不必在测试类中使用/加载Spring。
您将自己实例化要测试的类,而不是使用Spring,如果需要,可以使用模拟库将测试中的实例与其依赖项隔离开来。
这是要遵循的方法,因为它很快并且有利于隔离测试组件。
例如,一个注释为Spring服务的FooService执行一些计算并且依赖于FooRepository来检索一些数据可以在没有Spring的情况下进行测试:

 @Service public class FooService{ private FooRepository fooRepository; public FooService(FooRepository fooRepository){ this.fooRepository = fooRepository; } public long compute(...){ List foos = fooRepository.findAll(...); // core logic long result = foos.stream() .map(Foo::getValue) .filter(v->...) .count(); return result; } } 

您可以模拟FooRepository并对FooRepository的逻辑进行unit testing。
使用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 fooData = ...; Mockito.when(fooRepository.findAll(...)) .thenReturn(fooData); long actualResult = fooService.compute(...); long expectedResult = ...; Assertions.assertEquals(expectedResult, actualResult); } } 

如何确定组件是否可以进行普通测试(没有弹簧)或仅使用Spring进行测试?

您认识到要测试的代码与Spring容器没有任何依赖关系,因为组件/方法不使用Springfunction来执行其逻辑。
在前面的示例中, FooService执行一些不需要执行Spring的计算和逻辑。 实际上无论有没有容器, compute()方法都包含我们想要断言的核心逻辑。
反过来,如果没有Spring,你将很难测试FooRepository ,因为Spring Boot为你配置了数据源,JPA上下文和仪器你的FooRepository接口为它提供了一个默认实现和多个其他东西。
测试控制器(rest或MVC)也是一样的:如何在没有Spring的情况下将控制器绑定到端点? 如何在没有Spring的情况下解析HTTP请求并生成HTTP响应? 你根本做不到。

编写完整的集成测试

编写端到端测试需要使用应用程序的整个配置和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注释)。

完整集成测试必须由CI构建执行

加载完整的弹簧上下文需要时间。 因此,您应该对@SpringBootTest保持谨慎,因为这可能会使unit testing执行时间过长,并且通常您不希望强烈降低开发人员计算机上的本地构建以及使测试编写愉快的测试反馈。对开发人员有效。
这就是为什么“慢”测试通常不会在开发人员机器上执行的原因。
因此,您应该进行集成测试( IT后缀而不是测试类命名中的Test后缀),并确保仅在持续集成构建中执行这些测试。
但是,由于Spring Boot在您的应用程序中执行许多操作(其余控制器,mvc控制器,JSON序列化/反序列化,持久性等等),您可以编写许多unit testing,这些测试仅在CI构建上执行,而不是很好。
仅在CI构建上执行端到端测试是可以的,但是只有CI构建上执行的持久性,控制器或JSON测试也不行。
实际上,开发人员的构建速度很快但是作为一个缺点,本地的测试执行只能检测到一小部分可能的回归……
为了防止这种警告,Spring Boot提供了一种中间方式:部分集成测试或切片测试(他们称之为):下一点。

编写关注特定层或关注点的部分集成测试(切片测试)

正如“识别可以进行普通测试的测试(没有弹簧)”这一点所解释的那样,某些组件只能通过正在运行的容器进行测试。
但是为什么使用@SpringBootTest将加载应用程序的所有bean和配置,而您只需要加载几个特定的​​配置类和bean来测试这些组件?
例如,为什么要加载完整的Spring JPA上下文(bean,配置,在内存数据库中等等)来统一测试控制器?
相反,为什么加载与Spring控制器相关的所有配置和bean来统一测试JPA存储库?
Spring Boot通过切片测试function解决了这一问题。
这些并不比普通的unit testing(没有容器)快,但这些比加载整个上下文要快得多。 因此,在本地计算机上执行它们通常是非常可接受的
每个切片测试风格都会加载一组非常有限的自动配置类,您可以根据需要修改这些类。

一些常见的切片测试function:

  • 自动配置的JSON测试:@JsonTest

要测试该对象JSON序列化和反序列化是否按预期工作,您可以使用@JsonTest批注。

  • 自动配置的Spring MVC测试:@WebMvcTest

要测试Spring MVC控制器是否按预期工作,请使用@WebMvcTest注释。

  • 自动配置Spring WebFlux测试:@WebFluxTest

要测试Spring WebFlux控制器是否按预期工作,可以使用@WebFluxTest批注。

  • Auto-configured Data JPA Tests : @DataJpaTest

您可以使用@DataJpaTest批注来测试JPA应用程序。

而且你还有许多Spring Boot为你提供的切片风味。
请参阅文档的测试部分以获取更多详细信息。