</dependency>
</dependencies>

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>
因为继承了`spring-boot-starter-parent`,所以我们依赖的`spring-boot-starter-test`不需要写具体的版本,可以直接集成父级的版本定义。其中,`spring-boot-starter-web`是用于提供 REST API 的 web 容器,`spring-boot-starter-test`可以提供各种测试框架的,`spring-boot-maven-plugin`是将 SpringBoot 应用打包为可执行 jar 的插件。


### 项目结构


因为是 DEMO 示例,我们实现一个 Echo 接口,能够接收请求参数,并返回加工后的字符串。按照惯例,我们使用万能的`Hello, World!`。


我们的项目结构如下:
├── pom.xml
 └── src
 ├── main
 │ ├── java
 │ │ └── cn
 │ │ └── howardliu
 │ │ └── effective
 │ │ └── spring
 │ │ └── springbootjunit5mockio
 │ │ ├── SpringbootJunit5MockioApplication.java
 │ │ ├── controller
 │ │ │ └── EchoController.java
 │ │ └── service
 │ │ ├── EchoService.java
 │ │ └── impl
 │ │ └── EchoServiceImpl.java
 │ └── resources
 │ └── application.yaml
 └── test
 └── java
 └── cn
 └── howardliu
 └── effective
 └── spring
 └── springbootjunit5mockio
 └── controller
 ├── EchoControllerMockTest.java
 └── EchoControllerNoMockitoTest.java
* SpringbootJunit5MockioApplication:SpringBoot 应用启动入口
* EchoController:接口定义
* EchoService:实现业务逻辑接口
* EchoServiceImpl:接口实现
* EchoControllerMockTest:使用 Mock 代理 EchoService 实现
* EchoControllerNoMockitoTest:直接测试接口实现


#### EchoServiceImpl


我们看下`EchoService`的实现,这将是我们 DEMO 的核心实现:
@Service
 public class EchoServiceImpl implements EchoService {
 @Override
 public String echo(String foo) {
 return "Hello, " + foo;
 }
 }
#### EchoControllerNoMockitoTest


我们先使用 Junit5+MockMvc 实现 Controller 接口的普通调用,代码如下:
@SpringBootTest(classes = SpringbootJunit5MockioApplication.class)
 @AutoConfigureMockMvc
 class EchoControllerNoMockitoTest {
 @Autowired
 private MockMvc mockMvc;
@Test
void echo() throws Exception {
    final String result = mockMvc.perform(
            MockMvcRequestBuilders.get("/echo/")
                    .param("name", "看山")
    )
            .andExpect(MockMvcResultMatchers.status().isOk())
            .andDo(MockMvcResultHandlers.print())
            .andReturn()
            .getResponse()
            .getContentAsString(StandardCharsets.UTF_8);

    Assertions.assertEquals("Hello, 看山", result);
}

}

我们通过`SpringBootTest`注解定义这是一个 SpringBoot 应用的测试用例,然后通过`AutoConfigureMockMvc`启动测试容器。这样,就可以直接注入`MockMvc`实例测试 Controller 接口。


这里需要注意一点,网上很多教程会让写`@ExtendWith({SpringExtension.class})`这样一个注解,其实完全没有必要。通过源码我们可以知道,`SpringBootTest`注解已经添加了`ExtendWith`。


#### EchoControllerMockTest


这个测试用例中,我们通过 Mockito 组件代理`EchoService`的`echo`方法,代码如下:

@SpringBootTest(classes = SpringbootJunit5MockioApplication.class)
@ExtendWith(MockitoExtension.class)
@AutoConfigureMockMvc
class EchoControllerMockTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private EchoService echoService;

@BeforeEach
void setUp() {
    Mockito.when(echoService.echo(Mockito.any()))
            .thenReturn("看山说:" + System.currentTimeMillis());
}

@Test
void echo() throws Exception {
    final String result = mockMvc.perform(
            MockMvcRequestBuilders.get("/echo/")
                    .param("name", "看山的小屋")
    )
            .andExpect(MockMvcResultMatchers.status().isOk())
            .andDo(MockMvcResultHandlers.print())
            .andReturn()
            .getResponse()
            .getContentAsString(StandardCharsets.UTF_8);

    Assertions.assertTrue(result.startsWith("看山"));
}

}

在这个示例中,我们需要注意`@ExtendWith(MockitoExtension.class)`注解,这个注解是用于引入`MockBean`的,我们通过对`echo`方法的拦截,使其返回我们定义好的响应结果。这种方式是为了在多系统或者多功能测试时,不需要真正调用接口。


比如,我们需要获取用户手机号,通常在接口中会校验用户有没有登录,我们就可以使用 Mockito 的能力代理登录验证,使结果永远是 true。