Mockito原理剖析——基本原理、@Spy、@Mock、插桩顺序

Mock的基本原理

在这里插入图片描述
通过生成子类的方式,拦截具体的方法调用,将方法调用转发至Mock的桩代码,从而实现方法返回值或者方法执行体的自定义。

Mock和Spy

区别

Mock和Spy的基本区别——在没有进行任何方法调用Mock的情况下,Mock根据全局配置根据方法调用返回值类型返回一个默认值,不会进行具体的方法调用;而Spy对象会默认调用真实的方法。

调用方法的行为
场景SpyMock
普通类调用真实的方法根据调用方法返回值类型直接返回默认值
抽象类对于非抽象方法,调用真实的方法;对于抽象方法,根据调用方法返回值类型直接返回默认值任何类型的方法都是根据调用方法返回值类型直接返回默认值
接口根据调用方法返回值类型直接返回默认值根据调用方法返回值类型直接返回默认值
使用场景实例
/**
 * @author lidelin.
 */
public class WithDepTest {

		/**
		 * 使用了Spy.
		 * 因为需要对同一个对象同时进行方法测试和方法Mock.
		 **/
    @Spy
    @InjectMocks
    private WithDepService withDepService;

		/**
		 * 使用了Mock.
		 * 因为不需要关注方法逻辑,所有方法都是需要被Mock的.
		 **/
    @Mock
    private IOtherService iOtherService;

    @Before
    public void setup() {
        MockitoAnnotations.initMocks(this);
    }

    @Test
    public void testMethod1() {
        // 进行接口对象方法的Mock
        Mockito.doAnswer(inv -> null).when(iOtherService).service();
        withDepService.method1();
    }

    @Test
    public void testMethod0() {
        // 因为依赖method1,而method1在上面已经进行了单测,所以直接进行具体场景的mock
        Mockito.doAnswer(inv -> null).when(withDepService).method1();
        // method0还是进行的实际的方法调用
        // 但是涉及到method1的调用都会被拦截
        withDepService.method0();
    }

    public static class WithDepService {

        private IOtherService service;

        public void method0() {
            method1();
        }

        public void method1() {
            // do something.
            service.service();
        }

    }

    public interface IOtherService {
        void service();
    }
}

Stubbing

插桩顺序的问题

给出一个场景(该场景仅存在于使用 @Spy mock对象的时候)

/**
 * @author lidelin.
 */
public class SpyTest2 {

    @Spy
    private TestLogic logic;

    @Before
    public void setup() {
        MockitoAnnotations.initMocks(this);
    }

    @Test
    public void test() {
        // NPE
        Mockito.when(logic.needMock(Mockito.any())).thenReturn(0);
        // 正常
        Mockito.doReturn(0).when(logic).needMock(Mockito.any());
    }

    public static class TestLogic {
        public int needMock(String a) {
            return a.length();
        }
    }
}

这个场景可以抽象成下面两种插桩方式的区别:

// 插桩先行
Mockito.doAnswer(answer).when(mock).callMethod(ArgMatcher...);
// 插桩后行
Mockito.when(mock.callMethod(ArgMatcher...)).doAnswer(answer);
插桩先行

在这里插入图片描述

从执行流程图可以看出,在插桩先行的场景中,不管是@Spy还是@Mock,在实际Mock的过程中都不会进行实际方法的调用。

插桩后行

在这里插入图片描述
从执行流程图可以看出,在插桩后行的场景中,@Spy在Mock的过程中都会进行实际方法的调用,由于Mockito.any()会默认传入一个null值的参数,该参数会被传入到实际的方法调用中从而造成NPE。

总结

1、Mockito的基本原理就是使用方法拦截器,判断每次方法调用的状态来对方法进行相应的操作

2、@Spy@Mock的本质区别就是在未进行插桩之前,默认操作是否会进行实际方法的调用

3、需要注意插桩顺序,防止业务代码逻辑导致Mock过程出现错误

Reference

Mokito文档

  • 3
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

木子的木木

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值