在Spring中,可以在Standalone模式下使用MockMVC来进行服务内测试,此时我们不会加载任何Context。来看个例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 |
@RunWith(MockitoJUnitRunner.class) public class WorkerControllerMockMvcStandaloneTest { private MockMvc mockMvc; @Mock private IWorkerService workerService; @InjectMocks private WorkerController workerController; private JacksonTester<Worker> jsonWorker; @Before public void setup() { JacksonTester.initFields(this, new ObjectMapper()); mockMvc = MockMvcBuilders.standaloneSetup(workerController) .setControllerAdvice(new WorkerControllerAdvisor()) .addFilter(new WorkerFilter()) .build(); System.out.println(); } @Test public void getWhenExists() throws Exception { //given given(workerService.get(2)).willReturn(new Worker("LiLei", 16)); //when MockHttpServletResponse response = mockMvc.perform(get("/worker/2").accept(MediaType.APPLICATION_JSON)) .andReturn().getResponse(); //then assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value()); assertThat(response.getContentAsString()).isEqualTo(jsonWorker.write(new Worker("LiLei", 16)).getJson()); } @Test public void getWhenNotExists() throws Exception { //given given(workerService.get(2)).willThrow(new NonExistingWorkerException()); //when MockHttpServletResponse response = mockMvc.perform(get("/worker/2").accept(MediaType.APPLICATION_JSON)) .andReturn().getResponse(); //then assertThat(response.getStatus()).isEqualTo(HttpStatus.NOT_FOUND.value()); assertThat(response.getContentAsString()).isEmpty(); } @Test public void getByNameWhenExists() throws Exception { //given given(workerService.getByName("LiLei")).willReturn(Optional.of(new Worker("LiLei", 16))); //when MockHttpServletResponse response = mockMvc.perform(get("/worker?name=LiLei").accept(MediaType.APPLICATION_JSON)) .andReturn().getResponse(); //then assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value()); assertThat(response.getContentAsString()).isEqualTo(jsonWorker.write(new Worker("LiLei", 16)).getJson()); } @Test public void getByNameWhenNotExists() throws Exception { //given given(workerService.getByName("LiLei")).willReturn(Optional.empty()); //when MockHttpServletResponse response = mockMvc.perform(get("/worker?name=LiLei").accept(MediaType.APPLICATION_JSON)) .andReturn().getResponse(); //then assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value()); assertThat(response.getContentAsString()).isEmpty(); } @Test public void add() throws Exception { MockHttpServletResponse response = mockMvc.perform( post("/worker").contentType(MediaType.APPLICATION_JSON) .content(jsonWorker.write(new Worker("Jerry", 12)).getJson()) ).andReturn().getResponse(); assertThat(response.getStatus()).isEqualTo(HttpStatus.CREATED.value()); } @Test public void workerFilter() throws Exception { //when MockHttpServletResponse response = mockMvc.perform(get("/worker/2").accept(MediaType.APPLICATION_JSON)) .andReturn().getResponse(); //then assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value()); assertThat(response.getHeaders("X-CHOBIT-APP")).containsOnly("chobit-header"); } } |
MockitoJUnitRunner和MockMVC
代码中使用了MockitoJUnitRunner
来运行单元测试。这个类是由Mockito提供的,并在内置的JUnit Runner上添加了一些功能:比如检测框架运行,初始化所有用@Mock
注解声明的变量,这样就不需要再调用Mockito.initMocks()
方法来显式mock对象了了。
代码中使用@Mock
注解mock了一个IWorkerService
的实例。这个IWorkerService
的实例需要注入到WorkerController
类的实例中。所以我们在声明WorkerController
实例的时候使用了@InjectMocks
注解。这样,通过这个注解我们使用mock出的IWorkerService
实例替换了真正的WorkerServiceImpl实例。
在每个测试中,我们使用MockMvc对象来执行各种各样的模拟请求(如GET、POST等请求),之后我们也会收到一个MockHttpServletResponse
对象作为返回值。注意这个返回值也不是一个真正的返回值,也是模拟的。
JacksonTester初始化
代码中使用JacksonTester.initFields()
方法创建了一个JacksonTester
对象。JacksonTester
是Spring提供的一个工具类,可以使用这个JacksonTester对象来生成Worker
实例的json字符串。
配置standalone MockMVC实例
每个测试在执行前都会调用setup方法。在setup方法中我们采用Standalone模式创建了MockMVC实例,并在配置中添加了要进行测试的Controller实例,以及新建的Controller Advice实例和HTTP Filter实例。当然也可以用类成员的形式创建Controller Advice和HTTPFilter实例。现在我们可以看到这种测试方法的不足了:所有Controller Advice和Filter中的逻辑都需要在setup()方法中进行配置。这是因为我们这里没有任何Spring Context可以注入Advice和Filter。
使用MockMVC测试ControllerAdvice和Filter
在测试方法getWhenNotExists()
中,我们测试了id不存在的情况下,请求返回的状态码是否是HttpStatus.NOT_FOUND
。如果返回值与预期一致的话,说明ControllerAdvice是能够正常工作的。
在最后的测试方法workerFilter()
中,我们测试了返回结果中有没有WorkerFilter添加的请求头信息。如果返回结果的header中有添加的请求头信息,说明Filter是能够正常工作的。
也可以做个验证,注释掉setup()方法中关于ControllerAdvice和Filter的配置,然后再运行getWhenNotExists()
和workerFilter()
方法。可以看到测试会执行失败,因为相关的ControllerAdvice和Filter无法注入到运行环境中。
另外,在这里进行ControllerAdvice和Filter的测试多少有些不妥,因为我们的目标是测试Controller层的执行逻辑。对这二者的测试可以留到集成测试中进行。
总结
使用MockMVC Standalone模式测试对Controller层进行测试不会使用任何Spring容器,Controller层所依赖的其他服务均是mock出来的,也就是说除了Controller层的逻辑,其他相关数据都是模拟的。描述如下图:
这种测试方法的优势是:
- 不依赖Spring容器,启动和测试耗时较少
- 集中于Controller层逻辑,不受其他服务接口的影响
当然其优势也是其不足所在,我们通常不会在Controller层写入太多的业务逻辑,而是将业务逻辑写在Service层。但是因为这种测试方案中没有加载Spring容器,所以无法注入依赖的Service类,因此就无法测试到Service中实现的业务逻辑。
- Spring Controller测试 – 01 概述
- Spring Controller测试 – 02 Standalone MockMVC
- Spring Controller测试 – 03 WebContext & MockMVC
- Spring Controller测试 – 04 SpringBootTest & MockMVC
- Spring Controller测试 – 05 SpringBootTest & WebServer
其他:示例代码可在CSDN下载,地址:https://download.csdn.net/download/tianxiexingyun/11065824
发表评论