如何在Spring Boot中测试组件/Bean [英] How to test a component / bean in Spring Boot
问题描述
要在Spring Boot应用程序中测试组件/bean,请 Spring Boot文档的测试部分提供了很多信息和多种方式:
@Test
,@SpringBootTest
,@WebMvcTest
,@DataJpaTest
以及许多其他方式.
为什么提供这么多方式?
如何决定青睐的方式?
我是否应该将以Spring Boot测试注释(例如@SpringBootTest
,@WebMvcTest
,@DataJpaTest
)注释的测试类视为集成测试?
To test a component/bean in a Spring Boot application, the testing part of the Spring Boot documentation provides much information and multiple ways :
@Test
, @SpringBootTest
, @WebMvcTest
, @DataJpaTest
and still many other ways.
Why provide so many ways ?
How decide the way to favor ?
Should I consider as integration tests my test classes annotated with Spring Boot test annotations such as @SpringBootTest
, @WebMvcTest
, @DataJpaTest
?
PS:之所以创建此问题,是因为我注意到许多开发人员(即使是经验丰富的开发人员)也不会因为使用注释而不是注释而产生后果.
PS : I created this question because I noticed that many developers (even experienced) don't get the consequences to use an annotation rather than another.
推荐答案
TL-DR
- 为不需加载Spring容器即可直接测试的组件编写普通的单元测试(在本地和CI构建中运行它们).
-
TL-DR
- write plain unit tests for components that you can straightly test without loading a Spring container (run them in local and in CI build).
write partial integration tests/slicing unit test for components that you cannot straightly test without loading a Spring container such as components related to JPA, controllers, REST clients, JDBC ... (run them in local and in CI build)
为一些带来价值的高级组件编写一些完整的集成测试(端到端测试)(在CI构建中运行它们).
write some full integration tests (end-to-end tests) for some high-level components where it brings values (run them in CI build).
- 普通单元测试(不加载Spring容器)
- 全面集成测试(加载具有所有配置和bean的Spring容器)
- 部分集成测试/测试切片(将具有严格限制的配置和bean的Spring容器加载)
使用Spring的一般方法可以在集成测试中测试任何组件,并且只有某些类型的组件适合于整体测试(不带容器).
但是请注意,无论有没有弹簧,单一测试和集成测试都不是相反的,而是互补的.In a general way with Spring any component can be tested in integration tests and only some kinds of components are suitable to be tested unitary(without container).
But note that with or without spring, unitary and integration tests are not opposed but complementary.在应用程序中使用Spring Boot并不意味着您需要为运行的任何测试类加载Spring容器.
在编写不需要来自Spring容器的任何依赖项的测试时,您没有在测试类中使用/加载Spring.
代替使用Spring,您将自己实例化要测试的类,并在需要时使用模拟库将受测实例与其依赖项隔离.
之所以采用这种方法,是因为它速度快并且有利于隔离测试的组件.
例如,可以在没有Spring的情况下测试被标注为Spring服务的FooService
,该服务执行一些计算并且依靠FooRepository
来检索一些数据:Using Spring Boot in your application doesn't mean that you need to load the Spring container for any test class you run.
As you write a test that doesn't need any dependencies from the Spring container, you don't have to use/load Spring in the test class.
Instead of using Spring you will instantiate yourself the class to test and if needed use a mock library to isolate the instance under test from its dependencies.
That is the way to follow because it is fast and favors the isolation of the tested component.
For example, aFooService
annotated as Spring service that performs some computations and that rely onFooRepository
to retrieve some data can be tested without Spring :@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; } }
您可以模拟
FooRepository
并对FooService
的逻辑进行单元测试.
使用JUnit 5和Mockito时,测试类如下所示:You can mock
FooRepository
and unit test the logic ofFooService
.
With JUnit 5 and Mockito the test class could look like :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); } }
如何确定某个组件是可以简单测试(不带弹簧)还是只能通过Spring进行测试?
由于组件/方法不使用Spring功能来执行其逻辑,因此您识别出要测试的代码与Spring容器没有任何依赖关系.
在前面的示例中,FooService
执行一些不需要执行Spring的计算和逻辑.
确实,不管有没有容器,compute()
方法都包含我们要声明的核心逻辑.
相反,如果没有Spring,您将很难测试FooRepository
,因为Spring Boot为您配置数据源,JPA上下文,并检测您的FooRepository
接口以为其提供默认实现和其他多种功能.
测试控制器(静止或MVC)的方法相同.
没有Spring,如何将控制器绑定到端点?在没有Spring的情况下,控制器如何解析HTTP请求并生成HTTP响应?根本做不到.How to determine if a component can be plain tested (without spring) or only tested with Spring?
You recognize a code to test that doesn't have any dependencies from a Spring container as the component/method doesn't use Spring feature to perform its logical.
In the previous example,FooService
performs some computations and logic that don't need Spring to be executed.
Indeed with or without container thecompute()
method contains the core logic we want to assert.
Reversely you will have difficulties to testFooRepository
without Spring as Spring Boot configures for you the datasource, the JPA context, and instrument yourFooRepository
interface to provide to it a default implementation and multiple other things.
Same thing for testing a controller (rest or MVC).
How could a controller be bound to an endpoint without Spring? How could the controller parse the HTTP request and generate an HTTP response without Spring? It simply cannot be done.编写端到端测试需要使用应用程序的整个配置和bean加载容器.
要实现@SpringBootTest
就是这样:Writing an end-to-end test requires to load a container with the whole configuration and beans of the application.
To achieve that@SpringBootTest
is the way :注释通过创建您的应用中使用的ApplicationContext起作用 通过SpringApplication进行测试
The annotation works by creating the ApplicationContext used in your tests through SpringApplication
您可以通过这种方式使用它来进行测试,而无需进行任何模拟:
You can use it in this way to test it without any mock :
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:
But you can also mock some beans of the container if it makes sense :
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
批注).Note the difference for mocking as you want to mock a plain instance of a
Bar
class (org.mockito.Mock
annotation)and that you want to mock aBar
bean of the Spring context (org.springframework.boot.test.mock.mockito.MockBean
annotation).加载完整的spring上下文需要花费时间.因此,您应谨慎使用
@SpringBootTest
,因为这可能会使单元测试的执行时间过长,并且通常您不希望强烈减慢开发人员计算机上的本地构建速度以及使测试编写变得愉快的重要测试反馈.对开发人员而言高效.
这就是为什么通常不会在开发人员的计算机上执行慢速"测试的原因.
因此,您应该对它们进行集成测试(在测试类的命名中使用IT
后缀而不是Test
后缀),并确保仅在连续集成版本中执行这些测试. 但是,由于Spring Boot对应用程序中的许多内容起作用(其余控制器,MVC控制器,JSON序列化/反序列化,持久性等),因此您可以编写许多仅在CI构建上执行的单元测试,而并非如此也可以.
仅在CI构建上执行端到端测试是可以的,但仅在CI构建上执行持久性,控制器或JSON测试也完全不可行.
确实,开发人员的构建速度很快,但是作为缺点,在本地执行测试只能检测到一小部分可能的回归...
为了避免这种警告,Spring Boot提供了一种中间方式:部分集成测试或切片测试(正如他们所说的那样):下一点.Loading a full spring context takes time. So you should be cautious with
@SpringBootTest
as this may make unit tests execution to be very long and generally you don't want to strongly slow down the local build on the developer's machine and the test feedback that matters to make the test writing pleasant and efficient for developers.
That's why "slow" tests are generally not executed on the developer's machines.
So you should make them integration tests (IT
suffix instead ofTest
suffix in the naming of the test class) and make sure that these are executed only in the continuous integration builds.
But as Spring Boot acts on many things in your application (rest controllers, MVC controllers, JSON serialization/deserialization, persistence, and so for...) you could write many unit tests that are only executed on the CI builds and that is not fine either.
Having end-to-end tests executed only on the CI builds is ok but having also persistence, controllers or JSON tests executed only on the CI builds is not ok at all.
Indeed, the developer build will be fast but as drawback the tests execution in local will detect only a small part of the possible regressions...
To prevent this caveat, Spring Boot provides an intermediary way : partial integration test or the slice testing (as they call it) : the next point.如识别可以简单测试的测试(无弹簧))"所述,某些组件只能在运行的容器中进行测试.
但是,为什么要使用@SpringBootTest
来加载应用程序的所有bean和配置,而只需要加载一些特定的配置类和bean来测试这些组件呢?
例如,为什么要加载完整的Spring JPA上下文(bean,配置,内存数据库等)以对控制器进行统一测试?
相反,为什么要加载与Spring控制器关联的所有配置和Bean来统一测试JPA存储库?
Spring Boot通过As explained in the point "Recognizing a test that can be plain tested (without spring))", some components can be tested only with a running container.
But why using@SpringBootTest
that will load all beans and configurations of your application while you would need to load only a few specific configuration classes and beans to test these components?
For example why loading a full Spring JPA context (beans, configurations, in memory database, and so forth) to unitary test a controller ?
And reversely why loading all configurations and beans associated to Spring controllers to unitary test a JPA repository?
Spring Boot addresses this point with the slice testing feature.
These are not as much as fast than a plain unit tests (without container) but these are really much faster than loading the whole context. So executing them on the local machine is generally very acceptable.
Each slice testing flavor loads a very restricted set of auto-configuration classes that you can modify if needed according to your requirements.一些常见的切片测试功能:
-
测试对象JSON序列化和反序列化是否正常 如预期的那样,您可以使用@JsonTest批注.
To test that object JSON serialization and deserialization is working as expected, you can use the @JsonTest annotation.
-
- Auto-configured Spring MVC Tests : @WebMvcTest
-
- Auto-configured Spring WebFlux Tests : @WebFluxTest
-
您可以使用
@DataJpaTest
批注来测试JPA应用程序.You can use the
@DataJpaTest
annotation to test JPA applications.Spring Boot还为您提供了许多其他切片功能.
请参阅文档的测试部分获取更多详细信息.And you have still many other slice flavors that Spring Boot provides to you.
See the testing part of the documentation to get more details.这篇关于如何在Spring Boot中测试组件/Bean的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!
要测试Spring WebFlux控制器是否按预期工作,您可以 可以使用
@WebFluxTest
批注.To test that Spring WebFlux controllers are working as expected, you can use the
@WebFluxTest
annotation.
要测试Spring MVC控制器是否按预期工作,请使用
@WebMvcTest
注释.To test whether Spring MVC controllers are working as expected, use the
@WebMvcTest
annotation.
-