Spring Restful Api 单元测试

和任何做产品的公司一样,软件公司对其软件产品的质量也是十分看重。
虽然任何公司都会有测试部门,但软件产品的质量不能完全靠QA同学们的加班来保障。软件质量从程序开发阶段就应该引起重视。
单元测试便是在开发阶段中保证质量的一种重要方式。

什么是单元测试

  • 一个单元指的是应用程序中可测试的最小的一组源代码。
  • 源代码中包含明确的输入和输出的每一个方法被认为是一个可测试的单元。
  • 单元测试也就是在完成每个模块后都进行的测试。从确保每个模块没有问题,从而提高整体的程序质量。

单元测试的目的

  • 是将应用程序的所有源代码,隔离成最小的可测试的单元,保证每个单元的正确性。
  • 理想情况下,如果每个单元都能保证正确,就能保证应用程序整体相当程度的正确性。
  • 单元测试也是一种特殊类型的文档,相对于书面的文档,测试脚本本身往往就是对被测试代码的实际的使用代码,对于帮助开发人员理解被测试单元的使用是相当有帮助的。

Spring Restful Api 服务单元测试

就以现在流行的Restful服务来举一个单元测试例子,主要包含ControllerService层。

测试工具,使用junit+mockito+powermock测试框架

  • 项目新增测试框架的pom依赖:
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.10</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-all</artifactId>
    <version>1.8.4</version>
</dependency>
<dependency>
    <groupId>org.powermock</groupId>
    <artifactId>powermock-module-junit4</artifactId>
    <version>1.5.6</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.powermock</groupId>
    <artifactId>powermock-api-mockito</artifactId>
    <version>1.5.6</version>
    <scope>test</scope>
</dependency>

创建单元测试基类

@ContextConfiguration(classes = {WebConfig.class })
@RunWith(SpringJUnit4ClassRunner.class)  
public class BaseCtlCfg extends AbstractContextControllerTests  {
}

Controller层单元测试

  • 测试类继承BaseCtlCfg,加载、初始化Spring相关的配置。
  • @Before中初始化Mock
  • Spring容器中获取Controller实例,通过反射机制用模拟的service类替换原有的service类。
  • 根据测试的Case构建模拟数据。
  • 通过Mockhttp method请求方法调用接口,断言返回的http statusbody是否符合期望。
public class DraftControllerTest extends BaseCtlCfg{
    @Mock
    private  DraftService draftService;
    @Autowired
    protected WebApplicationContext wac;
    @Before
    public void before(){
        //初始化Mock
        MockitoAnnotations.initMocks(this);
        //获取上下文容器中的controller
        DraftController draftController = (DraftController) wac.getBean("draftController");
        //用mock的Service替换原有的service
        ReflectionTestUtils.setField(draftController, "draftService", draftService);
        }
    @Test
    public void publishTest() throws Exception {
        DraftPublishReq request = new DraftPublishReq();
        request.setPublishMode(1);
        request.setClassIds("495331469627");
        request.setPublishTime(System.currentTimeMillis());
        request.setPublishTime(System.currentTimeMillis() + 2*24*60*60*1000);

        String json = JSON.toJSONString(request);
        String uri = "/v0.3/drafts/00691d21-71f4-4115-89d0-a7168c77ef8b/actions/publish";

        //构建模拟数据。
        DraftInfo draftInfo = new DraftInfo();
        draftInfo.setIsdel(false);
        //模拟方法调用,当draftId=00691d21-71f4-4115-89d0-a7168c77ef8b时,return draftInfo对象
        when(draftService.get("00691d21-71f4-4115-89d0-a7168c77ef8b")).thenReturn(draftInfo);

        String resStr = mockMvc.perform(post(uri, "json")
                .characterEncoding("UTF-8")
                .contentType(MediaType.APPLICATION_JSON)
                .accept(MediaType.valueOf(MockUtil.APPLICATION_JSON))
                .content(json.getBytes()))
                .andExpect(status().isOk())
                .andReturn().getResponse().getContentAsString();

        JSONArray jsonArray = JSON.parseArray(resStr);
        Assert.assertEquals(1, jsonArray.size());
    }
}

Service层测试

  • Mock类的静态方法需要引入注解@RunWith(PowerMockRunner.class)@PrepareForTest(LifecycleServiceV06.class)LifecycleServiceV06.class为Mock的静态方法所在的类)。
  • 通过@Spy注解Mock测试类的真实对象。
  • @Before中初始化Mock,并替换类中要Mock的属性。
  • 构建模拟数据
  • 方法如果有返回值,断言返回值是否符合期望。
@RunWith(PowerMockRunner.class)
@PrepareForTest(LifecycleServiceV06.class)
public class HomeworkBizServiceTest{

    @Spy //mock真实对象
    private HomeworkBizService homeworkBizService = new HomeworkBizService();
    @Mock
    private HomeworkExtendRepository homeworkExtendRepository;

    @Before
    public void before() {
        //初始化Mock
        MockitoAnnotations.initMocks(this);
        //用mock的repository替换原有的repository
        ReflectionTestUtils.setField(homeworkBizService, "homeworkExtendRepository", homeworkExtendRepository);
    }

    @Test
    public void createHomeworkPackage() throws Exception {
        Long userId = 2107222934l;
        UserInfo userInfo = new UserInfo();
        userInfo.setUserId(String.valueOf(userId));
        String homeworkId="8b310d5d-ebd2-4798-bff3-2863fe1e1cd7";
        //构建模拟数据
         HomeworkExtend extend = new HomeworkExtend();
        extend.setLcmsHomeworkId(homeworkId);
        LifecycleSession session = new LifecycleSession();
        //mock类静态方法
         PowerMockito.mockStatic(LifecycleServiceV06.class);
        //模拟方法调用返回
         PowerMockito.when(LifecycleServiceV06.uploadHomework(homeworkId, userId)).thenReturn(session);
        //模拟方法调用返回
         when(homeworkExtendRepository.get(homeworkId)).thenReturn(extend);
        //void方法do nothing
        doNothing().when(homeworkBizService).createHomeworkPackage(homeworkId,session);

        homeworkBizService.createHomeworkPackage(homeworkId, userInfo);
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342

推荐阅读更多精彩内容