Sprinb Boot 使用JUnit 5搭配Spring MockMvc測試RestController API範例。
環境如下:
- Java 1.8
- IntelliJ IDEA 2019.2.1(Community Edition)
- Spring Boot 2.2.1.RELEASE
- Maven
Spring Boot專案的建立請參考:
Spring Boot 2.2版本以後JUnit版本預設為5,2.2 以前的JUnit版本為4需調整依賴設定。
本範例專案的pom.xml
內容。
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.1.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.abc</groupId>
<artifactId>demo1</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo1</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<compilerVersion>1.8</compilerVersion>
<source>8</source>
<target>8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
建立一支被測試的Controller類別DemoController
,內含一支被測試的方法hello()
如下。
DemoController
package com.abc.demo.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class DemoController {
@GetMapping(value = "/hello")
public String hello() {
String hello = "hello";
System.out.println(hello);
return hello;
}
}
建立一支負責測試DemoController
的測試用類別DemoControllerTests
如下,類別名稱前加上@WebMvcTest
。利用MockMvc
發出GET請求給/hello
API,也就是DemoController.hello()
。
DemoControllerTests
package com.abc.demo.controller;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@WebMvcTest(DemoController.class)
class DemoControllerTests {
@Autowired
private MockMvc mockMvc;
@Test
void hello_ReturnHello() throws Exception {
mockMvc.perform(get("/hello"))
.andExpect(status().isOk())
.andExpect(content().string("hello"));
}
}
設定完以上後執行DemoControllerTests.hello_ResturnHello()
來測試/hello
API。執行時會啟動Spring Boot應用程式,結果印出如下。測試結果為通過。
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.2.1.RELEASE)
2020-01-07 12:41:46.311 INFO 88510 --- [ main] c.a.demo.controller.DemoControllerTests : Starting DemoControllerTests on ...
2020-01-07 12:41:46.329 INFO 88510 --- [ main] c.a.demo.controller.DemoControllerTests : No active profile set, falling back to default profiles: default
2020-01-07 12:41:47.468 INFO 88510 --- [ main] o.s.s.concurrent.ThreadPoolTaskExecutor : Initializing ExecutorService 'applicationTaskExecutor'
2020-01-07 12:41:47.743 INFO 88510 --- [ main] o.s.b.t.m.w.SpringBootMockServletContext : Initializing Spring TestDispatcherServlet ''
2020-01-07 12:41:47.743 INFO 88510 --- [ main] o.s.t.web.servlet.TestDispatcherServlet : Initializing Servlet ''
2020-01-07 12:41:47.774 INFO 88510 --- [ main] o.s.t.web.servlet.TestDispatcherServlet : Completed initialization in 31 ms
2020-01-07 12:41:47.807 INFO 88510 --- [ main] c.a.demo.controller.DemoControllerTests : Started DemoControllerTests in 1.817 seconds (JVM running for 3.42)
hello
2020-01-07 12:41:48.176 INFO 88510 --- [extShutdownHook] o.s.s.concurrent.ThreadPoolTaskExecutor : Shutting down ExecutorService 'applicationTaskExecutor'
Process finished with exit code 0
實務上Controller通常會依賴於Service類來取得資料庫內容之類的,而@WebMvcTest
注釋預設只會自動配置Spring MVC相關元件,例如@Controller
,@ControllerAdvice
,@JsonComponent
,Filter
,WebMvcConfigurer
及Spring Security,MockMvc
等;其他非屬Spring MVC的元件如@Component
,@Service
,@Repository
等並不會被自動配置。
因此使用@WebMvcTest
進行Controller的單元測試時,通常會搭配@MockBean
來mock依賴的非Spring MVC Bean。
例如新增一個Service類DemoService
內容如下,類別名稱前加上@Service
。
DemoService
package com.abc.demo.service;
import org.springframework.stereotype.Service;
@Service
public class DemoService {
public String getHello() {
return "hello";
}
}
把DemoController.hello()
回傳的內容改為依賴的DemoService.getHello()
提供。
DemoController
package com.abc.demo.controller;
import com.abc.demo.service.DemoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class DemoController {
@Autowired
private DemoService demoService;
@GetMapping(value = "/hello")
public String hello() {
String hello = demoService.getHello();
System.out.println(hello); // hello
return hello;
}
}
則DemoControllerTests
修改如下,以@MockBean
對DemoService
做成mock,並利用Mockito對mock bean做stubbing返回指定的值。
DemoControllerTests
package com.abc.demo.controller;
import com.abc.demo.service.DemoService;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@WebMvcTest(DemoController.class)
class DemoControllerTests {
@Autowired
private MockMvc mockMvc;
@MockBean
private DemoService demoService;
@Test
void hello_ReturnHello() throws Exception {
Mockito.when(demoService.getHello()).thenReturn("hello"); // Mockito stubbing
mockMvc.perform(get("/hello"))
.andExpect(status().isOk())
.andExpect(content().string("hello"));
}
}
如果沒對依賴的DemoService
進行mock,則執行時會出現NoSuchBeanDefinitionException
錯誤,原因如上所述,@WebMvcTest
測試並不會自動配置非Spring MVC元件如@Service
。
如果希望測試時自動配置全部Bean(例如整合測試),而不僅限於Spring MVC元件,則測試類可改用@AutoConfigureMockMvc
搭配@SpringBootTest
來執行測試。
例如下面把測試類DemoControllerTests
中原本的@WebMvcTest
替換成@AutoConfigureMockMvc
搭配@SpringBootTest
,因此在執行測試時也會自動配置測試對象依賴的非Spring MVC元件,也就是DemoService
。所以把mock bean及Mockito stubbing註解掉後,則測試/hello
接點過程中呼叫的DemoService.getHello()
是真實物件回傳的結果。
DemoControllerTests
package com.abc.demo.controller;
import com.abc.demo.service.DemoService;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@AutoConfigureMockMvc // <--
@SpringBootTest // <--
class DemoControllerTests {
@Autowired
private MockMvc mockMvc;
// @MockBean
// private DemoService demoService;
@Test
void hello_ReturnHello() throws Exception {
// Mockito.when(demoService.getHello()).thenReturn("hello");
mockMvc.perform(get("/hello"))
.andExpect(status().isOk())
.andExpect(content().string("hello"));
}
}
參考:
沒有留言:
張貼留言