JMockit提供了很多Mock注解,例如@Mocked, @Injectable,@Tested,@Mock, @Capturing, 那这些注解背后的Mock逻辑是什么呢?

     

       在搞清楚这个问题之前,我们得首先明白,Mock本质是对java字节码的修改(或重定义)。 那我们先看看java字节码里面有什么。拿一个简单的类来说,


//一个包含初始代码块的普通类 
public class AnOrdinaryClassWithBlock {
	private int i;

	public static int j;

	// 初始代码块
	{
		i = 1;
	}
	// 静态初始代码块
	static {
		j = 2;
	}

	// 构造函数
	public AnOrdinaryClassWithBlock(int i) {
		this.i = i;
	}
 
	public int getI() {
		return i;
	}

	public void setI(int i) {
		this.i = i;
	}

}


       它编译成.class文件后,字节码内容如下:

public class cn/jmockit/demos/AnOrdinaryClassWithBlock {
     <ClassVersion=52>
     <SourceFile=AnOrdinaryClassWithBlock.java>

     private int i;
     public static int j;

     static  { // <clinit> //()V
         L1 {
             iconst_2
             putstatic cn/jmockit/demos/AnOrdinaryClassWithBlock.j:int
         }
         L2 {
             return
         }
     }

     public AnOrdinaryClassWithBlock(int arg0) { // <init> //(I)V
         <localVar:index=0 , name=this , desc=Lcn/jmockit/demos/AnOrdinaryClassWithBlock;, sig=null, start=L1, end=L2>
         <localVar:index=1 , name=i , desc=I, sig=null, start=L1, end=L2>

         L1 {
             aload0 // reference to self
             invokespecial java/lang/Object.<init>()V
         }
         L3 {
             aload0 // reference to self
             iconst_1
             putfield cn/jmockit/demos/AnOrdinaryClassWithBlock.i:int
         }
         L4 {
             aload0 // reference to self
             iload1
             putfield cn/jmockit/demos/AnOrdinaryClassWithBlock.i:int
         }
         L5 {
             return
         }
         L2 {
         }
     }

     public getI() { //()I
         <localVar:index=0 , name=this , desc=Lcn/jmockit/demos/AnOrdinaryClassWithBlock;, sig=null, start=L1, end=L2>

         L1 {
             aload0 // reference to self
             getfield cn/jmockit/demos/AnOrdinaryClassWithBlock.i:int
             ireturn
         }
         L2 {
         }
     }

     public setI(int arg0) { //(I)V
         <localVar:index=0 , name=this , desc=Lcn/jmockit/demos/AnOrdinaryClassWithBlock;, sig=null, start=L1, end=L2>
         <localVar:index=1 , name=i , desc=I, sig=null, start=L1, end=L2>

         L1 {
             aload0 // reference to self
             iload1
             putfield cn/jmockit/demos/AnOrdinaryClassWithBlock.i:int
         }
         L3 {
             return
         }
         L2 {
         }
     }
}


         我们可以看到, 静态代码块以及静态变量的初始化代码,是放进了clinit方法里, 代码块以及成员变量初始化是放到init(即构造函数)方法里,此外类还有其它方法,未初始化的类的静态变量,成员变量。 

       
       JMockit对java字节码的修改,最终就是对方法的修改,注入JMockit自己的代码。下面,我们总结一下,各个注解的Mock逻辑。



Mock注解Mock范围Mock逻辑用途
@Mocked 类/接口
  1. 默认除了clinit方法外,其它所有方法都被Mock。如果注解的stubOutClassInitialization=true, 则clinit也会被mock.


  2. Mock后
    方法返回原始类型的就返回默认值,比如int就返回0.
    方法返回String的就返回null,

    方法返回其它引用的,就返回另一个@Mocked的对象(递归的定义)


把类的所有对象,所有方法都Mock
@Injectable对象
  1. 除clinit方法外,其它方法都会被Mock。init虽没有被Mock,但init并不会调用。即@Injectable修饰的对象的构建不会调用init方法。

  2. Mock后

    与@Mocked相同

对某一个对象进行Mock
@Tested不Mock

与@Injectable搭配使用。

 @Tested表示待测试的对象,该对象由JMockit自动帮你创建。


@Mock方法@Mock修饰的方法,代替原方法。
@Capturing类/接口及其所有子类是@Mocked的加强版,还作用于其所有子类用于Mock子类