Spring Boot中测试

本文翻译之:https://www.baeldung.com/spring-boot-testing

1、概述

该文中介绍如何使用Spring Boot进行测试,测试分为独立运行的单元测试和引导spring上下文的集成测试。

2、项目设置

3、Maven依赖

首先添加测试依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <scope>test</scope>
</dependency>

spring-boot-starter-test是包含测试需要的大部分元素的主要依赖。

H2 DB是我们的内存数据库,它消除了为测试目的而配置和启动实际数据库的需要。

3.1 Junit4

从Spring Boot 2.4,JUnit5的老式引擎已从spring-boot-starter-test中删除。如果仍然想使用JUnit4编写测试,需要添加以下Maven依赖项:

<dependency>
    <groupId>org.junit.vintage</groupId>
    <artifactId>junit-vintage-engine</artifactId>
    <scope>test</scope>
    <exclusions>
        <exclusion>
            <groupId>org.hamcrest</groupId>
            <artifactId>hamcrest-core</artifactId>
        </exclusion>
    </exclusions>
</dependency>

4、使用@SpringBootTest进行集成测试

集成测试侧重于集成应用程序的不同层,意味着不涉及Mock。

理想情况下,应该将集成测试与单元测试分开,并且不应该与单元测试一起运行,因为集成测试很耗时,并且可能需要一个实际的数据库。

集成测试需要启动一个容器来执行测试用例。

@RunWith(SpringRunner.class)
@SpringBootTest(
  SpringBootTest.WebEnvironment.MOCK,
  classes = Application.class)
@AutoConfigureMockMvc
@TestPropertySource(
  locations = "classpath:application-integrationtest.properties")
public class EmployeeRestControllerIntegrationTest {

    @Autowired
    private MockMvc mvc;

    @Autowired
    private EmployeeRepository repository;

    // write test cases here
}

引导整个容器时, @SpringBootTest注解很有用。注释通过创建将在我们的测试中使用的ApplicationContext来工作。

我们可以使用@SpringBootTest的webEnvironment属性来配置我们的运行环境;我们在这里使用WebEnvironment.MOCK以便容器将在模拟 servlet 环境中运行。

接下来,@TestPropertySource注释帮助配置特定于我们测试的属性文件的位置。请注意,使用*@TestPropertySource加载的属性文件将覆盖现有的application.properties*文件。

application-integrationtest.properties包含的细节配置持久性存储

spring.datasource.url = jdbc:h2:mem:test
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.H2Dialect

如果我们想针对 MySQL 运行集成测试,我们可以在属性文件中更改上述值。

集成测试的测试用例可能类似于控制器层单元测试:

@Test
public void givenEmployees_whenGetEmployees_thenStatus200()
  throws Exception {

    createTestEmployee("bob");

    mvc.perform(get("/api/employees")
      .contentType(MediaType.APPLICATION_JSON))
      .andExpect(status().isOk())
      .andExpect(content()
      .contentTypeCompatibleWith(MediaType.APPLICATION_JSON))
      .andExpect(jsonPath("$[0].name", is("bob")));
}

Controller层单元测试的不同之处在于,这里没有任何东西被模拟,并且将执行端到端的场景。

5、使用@TestConfiguration测试配置

正如我们在上一节中看到的,使用*@SpringBootTest注释的测试将引导完整的应用程序上下文,这意味着我们可以@Autowire*通过组件扫描获取的任何 bean 进入我们的测试:

@RunWith(SpringRunner.class)
@SpringBootTest
public class EmployeeServiceImplIntegrationTest {

    @Autowired
    private EmployeeService employeeService;

    // class code ...
}

但是,我们可能希望避免引导真实的应用程序上下文,而是使用特殊的测试配置。我们可以使用 @TestConfiguration注解来实现这一点。有两种使用注解的方法。在我们想要*@Autowire* bean 的同一测试类中的静态内部类上:

@RunWith(SpringRunner.class)
public class EmployeeServiceImplIntegrationTest {

    @TestConfiguration
    static class EmployeeServiceImplTestContextConfiguration {
        @Bean
        public EmployeeService employeeService() {
            return new EmployeeService() {
                // implement methods
            };
        }
    }

    @Autowired
    private EmployeeService employeeService;
}

或者,我们可以创建一个单独的测试配置类:

@TestConfiguration
public class EmployeeServiceImplTestContextConfiguration {
    
    @Bean
    public EmployeeService employeeService() {
        return new EmployeeService() { 
            // implement methods 
        };
    }
}

使用*@TestConfiguration注解的配置类 被排除在组件扫描之外,因此我们需要在每个我们想要@Autowire 的测试中显式地导入它。我们可以使用@Import* 注释来做到这一点 :

@RunWith(SpringRunner.class)
@Import(EmployeeServiceImplTestContextConfiguration.class)
public class EmployeeServiceImplIntegrationTest {

    @Autowired
    private EmployeeService employeeService;

    // remaining class code
}

6、使用@MockBean模拟

service层代码依赖于存储层:

@Service
public class EmployeeServiceImpl implements EmployeeService {

    @Autowired
    private EmployeeRepository employeeRepository;

    @Override
    public Employee getEmployeeByName(String name) {
        return employeeRepository.findByName(name);
    }
}

但是,要测试Service层,我们不需要知道或关心持久层是如何实现的。理想情况下,我们应该能够编写和测试我们的服务层代码,而无需在我们的完整持久层中布线。

为此,我们可以使用 Spring Boot Test 提供的Mock支持。

我们先看一下测试类骨架

@RunWith(SpringRunner.class)
public class EmployeeServiceImplIntegrationTest {

    @TestConfiguration
    static class EmployeeServiceImplTestContextConfiguration {
 
        @Bean
        public EmployeeService employeeService() {
            return new EmployeeServiceImpl();
        }
    }

    @Autowired
    private EmployeeService employeeService;

    @MockBean
    private EmployeeRepository employeeRepository;

    // write test cases here
}

要测试service类,我们需要创建一个Service类的实例并将其作为*@Bean 使用,以便我们可以在测试类中@Autowire它。我们可以使用@TestConfiguration*注解来实现这个配置。

这里另一个有趣的事情是*@MockBean的使用。它为EmployeeRepository*创建了一个 Mock,可用于绕过对实际EmployeeRepository的调用:

@Before
public void setUp() {
    Employee alex = new Employee("alex");

    Mockito.when(employeeRepository.findByName(alex.getName()))
      .thenReturn(alex);
}

由于设置完成,测试用例会更简单:

@Test
public void whenValidName_thenEmployeeShouldBeFound() {
    String name = "alex";
    Employee found = employeeService.getEmployeeByName(name);
 
     assertThat(found.getName())
      .isEqualTo(name);
 }

7、使用@DataJpaTest进行集成测试

8、使用@WebMvcTest进行单元测试

Controller依赖于Service层;

@RestController
@RequestMapping("/api")
public class EmployeeRestController {

    @Autowired
    private EmployeeService employeeService;

    @GetMapping("/employees")
    public List<Employee> getAllEmployees() {
        return employeeService.getAllEmployees();
    }
}

要测试控制器,我们可以使用*@WebMvcTest*。它将为我们的单元测试自动配置 Spring MVC 基础设施。

在大多数情况下,@ WebMvcTest将被限制为引导单个控制器。我们还可以将它与*@MockBean*一起使用,为任何所需的依赖项提供模拟实现。

@WebMvcTest还自动配置MockMvc,它提供了一种无需启动完整 HTTP 服务器即可轻松测试 MVC 控制器的强大方法。

话虽如此,让我们编写我们的测试用例:

@Test
public void givenEmployees_whenGetEmployees_thenReturnJsonArray()
  throws Exception {
    
    Employee alex = new Employee("alex");

    List<Employee> allEmployees = Arrays.asList(alex);

    given(service.getAllEmployees()).willReturn(allEmployees);

    mvc.perform(get("/api/employees")
      .contentType(MediaType.APPLICATION_JSON))
      .andExpect(status().isOk())
      .andExpect(jsonPath("$", hasSize(1)))
      .andExpect(jsonPath("$[0].name", is(alex.getName())));
}

*get(…)*方法调用可以替换为与 HTTP 动词对应的其他方法,如put ()、*post()*等。请注意,我们也在请求中设置内容类型。

MockMvc很灵活,我们可以使用它创建任何请求。

9、自动配置的测试

Spring Boot 的自动配置注解的惊人特性之一是它有助于加载部分完整应用程序和代码库的特定测试层。

除了上面提到的注解之外,这里列出了一些广泛使用的注解:

  • @WebF luxTest:我们可以使用 @WebFluxTest注解来测试 Spring WebFlux 控制器。它通常与*@MockBean*一起使用 ,为所需的依赖项提供模拟实现。

  • @JdbcTest我们可以使用*@JdbcTest注释来测试 JPA 应用程序,但它适用于只需要DataSource 的测试。注解配置了一个内存嵌入式数据库和一个JdbcTemplate。*

  • @JooqTest:要测试与 jOOQ 相关的测试,我们可以使用 @JooqTest注解,它配置了一个 DSLContext。

  • @DataMongoTest:要测试 MongoDB 应用程序,@DataMongoTest是一个有用的注解。默认情况下,如果驱动程序通过依赖项可用,它会配置内存中嵌入式 MongoDB,配置MongoTemplate,扫描@Document类,并配置 Spring Data MongoDB 存储库。

  • @DataRedisTest使测试 Redis 应用程序变得更加容易。默认情况下,它会扫描*@RedisHash*类并配置 Spring Data Redis 存储库。

  • @DataLdapTest 配置内存中的嵌入式LDAP(如果可用),配置LdapTemplate,扫描*@Entry*类,并默认配置 Spring Data LDAP存储库。

  • @RestClientTest:我们通常使用*@RestClientTest注解来测试 REST 客户端。它自动配置不同的依赖项,例如 Jackson、GSON 和 Jsonb 支持;配置一个RestTemplateBuilder;并默认 添加对MockRestServiceServer的支持。*

  • @JsonTest:仅使用测试 JSON 序列化所需的那些 bean 初始化 Spring 应用程序上下文。

您可以在我们的文章Optimizing Spring Integration Tests中阅读有关这些注释以及如何进一步优化集成测试的更多信息。

10. 结论

在本文中,我们深入探讨了 Spring Boot 中的测试支持,并展示了如何高效地编写单元测试。

本文的完整源代码可以在 GitHub 上找到。源代码包含更多示例和各种测试用例。

如果你想继续学习测试,我们有单独的文章与集成测试优化 Spring 集成测试JUnit 5 中的单元测试相关。

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐