Mockito (二十四)

Mockito项目实战demo(怎么用mock代替本类方法调用,即this调用)

情形一

被测试类如下:

@Service
@Transactional
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class CommentServiceImpl extends ServiceImpl<CommentMapper, Comment> implements ICommentService {
  
  
  private final ICommentService commentService;
  private final IHistoryService historyService;
  
  // 构造方法注入,单测时就可以注入mock对象,不然需要提供set方法注入mock对象
  public CommentServiceImpl(ICommentService commentService, IHistoryService historyService) {
        this.commentService = commentService;
        this.historyService = historyService;
    }
  
  @Override
  public MobileResponse commentDelete(Long id, Long userId) {
     //查询原留言
     Comment originalComment = commentService.selectById(id);  //本来是this调用,改为实例对象调用,方便mock
     //查询是否是自己的评论
     if (originalComment.getCreateUserId().equals(userId.intValue())) {

             boolean result = commentService.deleteById(id);//本来是this调用,改为实例对象调用,方便mock
             History history = new History(userId.intValue(), userId.intValue(), OperationTypeEnum.DELETE_COMMENT.getValue(),
                     originalComment.getContent(), "-", originalComment.getInfoId());
             result = historyService.insert(history);
        return MobileResponse.success(result);
     }
     return MobileResponse.error(MobileError.CAN_ONLY_DELETE_THEIR_OWN);
  }
  
}

测试类如下:

package com.stylefeng.guns.modular.detailRules.comment.service.impl;

import cn.hutool.core.lang.Assert;
import com.stylefeng.guns.core.base.protocol.MobileResponse;
import com.stylefeng.guns.modular.detailRules.comment.model.Comment;
import com.stylefeng.guns.modular.detailRules.comment.service.ICommentService;
import com.stylefeng.guns.modular.detailRules.history.model.History;
import com.stylefeng.guns.modular.detailRules.history.model.enums.OperationTypeEnum;
import com.stylefeng.guns.modular.detailRules.history.service.IHistoryService;
import org.junit.Test;

import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

public class CommentServiceImplTest {

    /**
    * 该测试方法模拟用户有删除权限的场景;
    *
    * mock对象调用方法的参数类型和值内容必须完全一致,才能得到mock预计的返回值;
    * 比如这个:when(commentService.selectById(id)).thenReturn(originalComment);
    * 如果id为int型就不会触发该预计的返回,或者实际传递的id为10,该when中传递的id为9,内容不一致,也不会触发该预计的返回;
    * @param
    * @return void
    */
    @Test
    public void commentDelete() {
        /**
         * 构造mock对象
         */
        Comment originalComment = mock(Comment.class);
        when(originalComment.getCreateUserId()).thenReturn(130);
        ICommentService commentService = mock(ICommentService.class);
        long id = 10;
        when(commentService.selectById(id)).thenReturn(originalComment);
        when(commentService.deleteById(id)).thenReturn(true);
        IHistoryService historyService = mock(IHistoryService.class);
        Long userId = 130L;
        History history = new History(userId.intValue(), userId.intValue(), OperationTypeEnum.DELETE_COMMENT.getValue(),
                originalComment.getContent(), "-", originalComment.getInfoId());
        when(historyService.insert(history)).thenReturn(true);

        /**
         * 构造真实对象,调用方法
         */
        ICommentService commentService1 = new CommentServiceImpl(commentService, historyService);
        MobileResponse mobileResponse = commentService1.commentDelete(10L, 130L);

        /**
         * 验证mock对象的行为
         */
        // 这两行一样,都是判断执行上面真实方法后,mock对象commentService是不是调用了1次deleteById(id)方法
        // 处理times判断次数,还有很多方法可以判断mock对象方法调用的情况
//      verify(commentService).deleteById(id);
        verify(commentService, times(1)).deleteById(id);
      
        /**
         * hutool对返回结果进行断言
         */
        Assert.isTrue(mobileResponse.getCode().equals("0"));
        Assert.isTrue(mobileResponse.getData().equals(true));
    }
}

测试结果(秒级完成)

情形二

如果被单测的方法中使用了this调用本类方法,如下:

@Service
@Transactional
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class CommentServiceImpl extends ServiceImpl<CommentMapper, Comment> implements ICommentService {
  
  private final IHistoryService historyService;
  
  // 构造方法注入,单测时就可以注入mock对象,不然需要提供set方法注入mock对象
  public CommentServiceImpl(IHistoryService historyService) {
        this.historyService = historyService;
    }

    public IHistoryService getHistoryService() {
        return historyService;
    }

    public void setHistoryService(IHistoryService historyService) {
        this.historyService = historyService;
    }
  
  @Override
  public MobileResponse commentDelete(Long id, Long userId) {
     //查询原留言
     Comment originalComment = this.selectById(id); // ① 使用了this,调用本类方法,而不是通过传入本类的一个实例对象调用
     //查询是否是自己的评论
     if (originalComment.getCreateUserId().equals(userId.intValue())) {

             boolean result = this.deleteById(id);  // ② 使用了this调用本类方法,而不是通过传入本类的一个实例对象调用
             History history = new History(userId.intValue(), userId.intValue(), OperationTypeEnum.DELETE_COMMENT.getValue(),
                     originalComment.getContent(), "-", originalComment.getInfoId());
             result = historyService.insert(history);
        return MobileResponse.success(result);
     }
     return MobileResponse.error(MobileError.CAN_ONLY_DELETE_THEIR_OWN);
  }
  
}

注意:①和②处,不写this也可以,测试方法一样,都是下面这种写法,此时也不需要注入本类的实例对象,只需要通过set方法设置需要用到的其他模拟对象,比如这里是historyService;

单测方法如下:

package com.stylefeng.guns.modular.detailRules.comment.service.impl;

import cn.hutool.core.lang.Assert;
import com.stylefeng.guns.core.base.protocol.MobileResponse;
import com.stylefeng.guns.modular.detailRules.comment.model.Comment;
import com.stylefeng.guns.modular.detailRules.history.model.History;
import com.stylefeng.guns.modular.detailRules.history.model.enums.OperationTypeEnum;
import com.stylefeng.guns.modular.detailRules.history.service.IHistoryService;
import org.junit.Test;
import org.mockito.Mockito;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;

import static org.mockito.Mockito.*;

public class CommentServiceImplTest {

    /**
    * 该测试方法模拟用户有删除权限的场景;
    *
    * mock对象调用方法的参数类型和值内容必须完全一致,才能得到mock预计的返回值;
    * 比如这个:when(commentService.selectById(id)).thenReturn(originalComment);
    * 如果id为int型就不会触发该预计的返回,或者实际传递的id为10,该when中传递的id为9,内容不一致,也不会触发该预计的返回;
    * @param
    * @return void
    */
    @Test
    public void commentDelete() {
        /**
         * 构造mock对象
         */
        Comment originalComment = mock(Comment.class);
        when(originalComment.getCreateUserId()).thenReturn(130);
        long id = 10;
        IHistoryService historyService = mock(IHistoryService.class);
        Long userId = 130L;
        History history = new History(userId.intValue(), userId.intValue(), OperationTypeEnum.DELETE_COMMENT.getValue(),
                originalComment.getContent(), "-", originalComment.getInfoId());
        when(historyService.insert(history)).thenReturn(true);

        /**
         * 构造真实对象,调用方法
         */
        CommentServiceImpl mock = mock(CommentServiceImpl.class, CALLS_REAL_METHODS);
        mock.setHistoryService(historyService);
        // 设置测试类中调用方法selectById(id)时返回originalComment模拟对象,测试类中可以使用this.selectById(id),也可以直接使用selectById(id)调用
        Mockito.doAnswer(new Answer() {
            @Override
            public Object answer(InvocationOnMock invocation) throws Throwable {
                return originalComment;
            }
        }).when(mock).selectById(id);
        // 设置测试类中调用方法deleteById(id)时返回true,测试类中可以使用this.deleteById(id),也可以直接使用deleteById(id)调用
        Mockito.doAnswer(new Answer() {
            @Override
            public Object answer(InvocationOnMock invocation) throws Throwable {
                return true;
            }
        }).when(mock).deleteById(id);
        MobileResponse mobileResponse = mock.commentDelete(10L, 130L);

        // 这两行一样,都是判断执行上面真实方法后,mock对象commentService是不是调用了1次deleteById(id)方法
//        verify(commentService).deleteById(id);
//        verify(commentService, times(1)).deleteById(id);

        /**
         * 对返回结果进行断言
         */
        Assert.isTrue(mobileResponse.getCode().equals("0"));
        Assert.isTrue(mobileResponse.getData().equals(true));
    }
}
带着疑问去思考,然后串联,进而归纳总结,不断追问自己,进行自我辩证,像侦查嫌疑案件一样看待技术问题,漆黑的街道,你我一起寻找线索,你就是技术界大侦探福尔摩斯
原文地址:https://www.cnblogs.com/cainiao-Shun666/p/14806725.html