Spring Controller测试01 – MockMVC Standalone

在Spring中,可以在Standalone模式下使用MockMVC来进行服务内测试,此时我们 不会加载任何Spring Context 。来看个例子:

@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 WorkerExceptionHandler())
                .addFilter(new WorkerFilter())
                .build();
 
        System.out.println();
        System.out.println("~~~~~~~~~~~~~~~~~~~~~~~~~~~~>>>> Unit Test Setup");
    }
 
 
    @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了一个 IWorkerService 实例。需要注意的是在 WorkerController 类中需要注入一个 IWorkerService 的实例。所以我们在声明 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和HTTP Filter实例。现在我们可以看到这种测试方法的不足了:所有Controller Advice和Filter中的逻辑都需要在这里进行配置。这是因为我们这里没有任何Spring Context可以注入Advice和Filter。

测试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层写入太多的业务逻辑,这种测试方式中没有用到Spring容器,所以无法注入依赖的其他接口,因此就无法测试到这些接口中实现的业务逻辑。

其特征可以参考下图:

Spring Controller测试01 – MockMVC Standalone

原文 

http://www.zhyea.com/2019/03/14/spring-controller-test-01-mockmvc-standalone.html

本站部分文章源于互联网,本着传播知识、有益学习和研究的目的进行的转载,为网友免费提供。如有著作权人或出版方提出异议,本站将立即删除。如果您对文章转载有任何疑问请告之我们,以便我们及时纠正。

PS:推荐一个微信公众号: askHarries 或者qq群:474807195,里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化这些成为架构师必备的知识体系。还能领取免费的学习资源,目前受益良多

转载请注明原文出处:Harries Blog™ » Spring Controller测试01 – MockMVC Standalone

赞 (0)
分享到:更多 ()

评论 0

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址