springboot2.1.3+Junit4 单元测试
阅读原文时间:2023年07月16日阅读:1

引入依赖的包:

org.mockito mockito-core 2.23.4 test
org.powermock powermock-api-mockito2 2.0.2 test
org.powermock powermock-core 2.0.2 test
org.powermock powermock-module-junit4 2.0.2 test

MockMvc + PowerMock + Mockito 来模拟post get请求,并mock掉service中存在doGet doPost外部系统的restful请求

@RunWith(PowerMockRunner.class)
@PowerMockRunnerDelegate(SpringRunner.class)
@prepareForTest({
HttpUtil.class // doGet doPost 使用util类,这里需要告诉UT
})
@PowerMockIgnore("javax.crypto.*")
@AutoConfigureMockMvc
@SpringBootTest
public class DemoSzlTest {
@Autowired
private WebApplicationContext webApplicationContext;
private MockMvc mockMvc;

  @Before  
  public void setUp() throws Exceptioin {  
       MockitoAnnotations.initMocks(this); // 初始化装载powerMockito  
       mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();  
  }

  @Test  
  @DirtiesContext  
  public void testPostDemo() throws Exception {  
        // mock HttpResponse 对象返回  
        StringEntity se = new StringEntity("xxxxxx", "utf-8");  
        HttpResponse resp = new BasicHttpResponse(new BasicStatusLine(HttpVersion.Http\_1\_1, HttpServletResponse.SC\_OK, ""));  
        resp.setEntity(se);  
        PowerMockito.mockStatic(HttpUtil.class);  
        // 这里的 Mockito.anyString() 要和 实际方法参数保持一致  
        PowerMockite.when(HttpUtil.doGet(Mockito.anyString(), Mockito.anyMap(), Mockito.any())).thenReturn(resp);  
        // 这里不 mock方法,直接走真实场景方法  
        PowerMockito.when(HttpUtil.toJson(Mockito.anyObject())).thenCallRealMethod();  
        // body contents  
       String jsonBody = "{\\"aaaa\\":\\"1111\\"}";  
       String token = "xxxxx";  
       MvcResult resultMsg = mockMvc.perform(  
                post("/xxx")  
                .contentType(MediaType.APPLICATION\_JSON\_VALUE)  
                .accept(MediaType.APPLICATION\_JSON\_UTF8\_VALUE)  
                .header("xxxxx", token)  
                .content(jsonBody)    // request body  
       ).andExpect(status().isCeated())  
        .andReturn();  
 }

    @Test
    @DirtiesContext
    public void contextLoads() throws Exception {
        // 这里定义的 header 值 必须要和service层使用的值保持一致,否则powerMock mock时会有问题
        // 其实也很好理解,既然要mock,那就做到真实一点,一切都保持一模一样即可。
        Header[] header = {
                new BasicHeader("Authorization", "aaabbbccc"),
                new BasicHeader("Content-Type", "application/json")
            };
        
        // 手动创建HttpResponse,来模式http请求的返回值
        StringEntity se = new StringEntity("{"
                + "\"memo\":\"这个只是demo式样\"}", "utf-8");
        HttpResponse resp = new BasicHttpResponse(new BasicStatusLine(HttpVersion.HTTP_1_1,
                HttpServletResponse.SC_OK, ""));
        resp.setEntity(se);
        
        // 和上面说明一致
        StringEntity se2 = new StringEntity("{"
                + "\"memo\":\"这个只是demo2式样\"}", "utf-8");
        HttpResponse resp2 = new BasicHttpResponse(new BasicStatusLine(HttpVersion.HTTP_1_1,
                HttpServletResponse.SC_OK, ""));
        resp2.setEntity(se2);
        
        // 开始mock我们想要的静态方法,此处是mock post请求并返回上面我们mock的值
        PowerMockito.mockStatic(HttpUtil.class);
        /**
         * 本人是为了达到一个service里连续调用2次post请求,但2次请求内容不同,返回结果也不同,进行mock
         * Mockito.eq("/aaa")               是mock值
         * Mockito.eq("aaabbb")             是mock值
         * Mockito.any(header.getClass())   是mock Header[]值
         */
        PowerMockito.when(HttpUtil.doPost(Mockito.eq("/aaa"), Mockito.eq("aaabbb"), Mockito.any(header.getClass()))).thenReturn(resp);
        PowerMockito.when(HttpUtil.doPost(Mockito.eq("/bbb"), Mockito.eq("cccddd"), Mockito.any(header.getClass()))).thenReturn(resp2);
        
        String jsonBody = "{\"aaaa\":\"1111\"}";
        String token = "xxxxx";
        MvcResult resultMsg = mockMvc.perform(
                 post("/testUTDemo")
                 .header("Authorization", token)
                 .content(jsonBody)    // request body
        ).andExpect(status().isCreated()).andReturn();
    }

}

Controller类:

package com.szl.demo.szldemo.controller;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import com.szl.demo.szldemo.common.dto.UserDto;
import com.szl.demo.szldemo.service.UserService;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@Controller
public class DemoController {
@Autowired
private UserService userService;

@ResponseBody  
@RequestMapping(value = "/testUTDemo", method = RequestMethod.POST)  
public UserDto testUTDemo(HttpServletRequest request, HttpServletResponse response) {  
    return userService.testDemoUnitTest();  
}

}

Service接口:

package com.szl.demo.szldemo.service;

import com.szl.demo.szldemo.common.dto.UserDto;

public interface UserService {

UserDto testDemoUnitTest();

}

Service实现类:

package com.szl.demo.szldemo.service.impl;

import org.apache.commons.io.IOUtils;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.message.BasicHeader;
import org.springframework.stereotype.Service;
import com.szl.demo.szldemo.common.dto.UserDto;
import com.szl.demo.szldemo.common.util.HttpUtil;
import com.szl.demo.szldemo.service.UserService;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@Service("userService")
public class UserServiceImpl implements UserService {

public UserDto testDemoUnitTest() {  
    try {  
        Header\[\] header = {  
                    new BasicHeader("Authorization", "aaabbbccc"),  
                    new BasicHeader("Content-Type", "application/json")  
                };  

        // 此处的代码块也是mock的重点  
        HttpResponse resp = HttpUtil.doPost("/aaa", "aaabbb", header);  
        HttpEntity entity = resp.getEntity();  
        String msg = IOUtils.toString(entity.getContent(), "UTF-8");  
        log.info("msg: " + msg);  

        // 同上  
        HttpResponse resp2 = HttpUtil.doPost("/bbb", "cccddd", header);  
        HttpEntity entity2 = resp2.getEntity();  
        String msg2 = IOUtils.toString(entity2.getContent(), "UTF-8");  
        log.info("msg2: " + msg2);  
    } catch (Exception e) {  
        e.printStackTrace();  
    }  

    //  这里随便赋值并返回,主要不是看这里的功能  
    UserDto dto = new UserDto();  
    dto.setId(1001L);  
    dto.setNickName("test");  
    dto.setUserId(10000001L);  
    return dto;  
}

}

HttpUtil.java类:

public class HttpUtil {
public static HttpResponse doGet(String url, Map params, Header[] headers) {
…..
这里省略
}

 public static JsonObject toJson(HttpResponse httpResponse) {  
     .....   
     这里省略  
 }  
 public static HttpResponse doPost(String url, String params, Header\[\] header) {  

        // 这里只是为了演示,手动创建StringEntity并赋值,手动创建HttpResponse并返回
        HttpResponse resp = new BasicHttpResponse(new BasicStatusLine(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, null));
        StringEntity se1 = new StringEntity("{"
                + "\"alg\":\"aqas\","
                + "\"id\":\"122011\"}", "utf-8");
        resp.setEntity(se1);
        return resp;
}
}

UserDto类:

package com.szl.demo.szldemo.common.dto;

import java.io.Serializable;
import lombok.Data;

@Data
public class UserDto implements Serializable {
private static final long serialVersionUID = -8858511759866491158L;

private Long id;  
private Long userId;  
private String nickName;

}

好了,让咋们来试试上面的UT跑出什么结果,如下图:

OK, 大功告成,如有博友需要,供参考,需转发,请注明引自链接,谢谢。

另附上额外功能,如果有朋友需要在UT里按顺序执行,需要加注解:

@FixMethodOrder(xxx)

这里的xxx有三种模式:

- 默认(MethodSorters.DEFAULT)
- 按方法名(MethodSorters.NAME_ASCENDING)
- JVM(MethodSorters.JVM)

一开始本人使用JVM模式,虽然能达到预期,但运行不太稳定,本人还是推荐大家使用NAME_ASCENDING模式

如有朋友参考本人的笔记,有问题可以留言,转载请注明原著,谢谢。