jmockit自查手册(待改进)

目录

Jmockit的两种Mock方式

基于行为

mock-record-replay-verify模型

如何Mock(mock阶段)

@Mocked PersonService personService

@Captured PersonService personService

@Injectable PersonService personService

@Tested PersonService personService

如何拦截重写(record阶段)

Expection初探

拦截重写被Mock类的静态方法

拦截重写被mock对象的无参方法

拦截重写被mock对象的多个方法

拦截重写被mock对象的方法中含有参数

拦截重写被mock对象的私有变量

拦截重写被mock对象的私有方法

如何Replay

如何Verify

调用次数的限制

显式验证

在verification中捕获调用参数

使用Delegate在Expectation中定制result

级联mock

部分mock

mock接口

自动注入被测试类

MockUp

直接用匿名内部类MockUp

伪造接口实现类

伪造类初始化过程

调用者的上下文

执行原有代码

多个类公用MockUp

伪造所有方法

Jmockit Coverage


Jmockit的两种Mock方式

1. 基于行为 Behavior-oriented 的测试(mock-record-replay-verify模型)

2. 基于状态 State-oriented 的测试(MockUp)

两种都十分重要,请慢慢看。

基于行为

mock-record-replay-verify模型

  • mock: 声明被mock的对象
  • record : 录制该mock对象将被调用的方法和返回值,它会类似aop一样将对应方法替换。
  • replay:调用record中录制的被Mock的方法
  • verify:行为验证

在record阶段实例化Expectations, 在verify阶段实例化Verifications。一个测试方法可以包括任意个(包括0)Expectation/Verification。

@Test
public void testMethod(Parameter p) {
    //常规准备代码
    
    //record
    new Expectations(){};
    
    //replay
    //调用测试代码
    
    //verify
    new Verifications(){};
    
    //其他验证代码
}

以下是一个简单案例,PersonService被mock了(mock阶段),其showAge方法本来应该返回10,但在Expectation中被改写为返回-1(record阶段),然后打印showAge(replay阶段)。

public class PersonService{
    public int showAge(int age){
        return 10;
    }
}

@RunWith(JMockit.class)
public class MockTest {
    // mock阶段
    @Mocked
    private PersonService personService;
 
    @Test
    public void testInstance(){
        // record阶段
        new Expectations(){
            {
                personService.showAge(anyInt); 
                result = -1;  // 调用personService.showAge(anyInt) 返回-1
            }
        };
        // replay截断
        System.out.println(personService.showAge(5) == -1); // 返回true  
        // verify截断
                
    }
}

如何Mock(mock阶段)

1. 如上图案例

@Mocked PersonService personService

仅针对PersonService这个类,可以创建一个假的PersonService对象,而原本showAge方法应该返回10,但由于使用了假的PersonService对象,所以会按照expectation所声明,返回-1。

注意事项

1.  mocked注解可以在全局变量处声明,可以在方法参数处声明

2. 在单元测试内部自己创建一个新对象,一样会被mock

3. 单元测试中,被mocked的对象,所有方法都会被mock:在Expectations(预期结果)中显式声明的方法,会被拦截重写;没有声明的方法,被调用时也不会走原有逻辑,而是直接返回null或者基础类型默认值,若想让其走原有逻辑,应写成@Tested @Mocked PersonService personService 

4. PersonService如果是个接口,其实现类为PersonServiceImpl,则无法mock其实现类此时需要求助于@Captured注解

public PersonService {
    public int showAge(){
        return 10;
    }
    public int showSex(){
        return 1;
    }
}

// 1. mock可以在全局变量处声明,可以在方法参数处声明
@Test
public  void testMocked(@Mocked PersonService personService){
    new Expectations(){
        {
            personService.showAge();
            return = -1;
        }
    };
    // 2. 在单元测试内部自己创建一个新对象,一样会被mock
    PersonService personService2 = new PersonService();
    Assert.assertTrue(personService2.showAge() == -1);

    // 3. 未在expectation中声明的方法被调用,不会走原有逻辑,而是返回null或者基础类型默认值
    Assert.assertTrule(personService.showSex() == 0);
}

// 4. PersonService如果是个接口,其实现类为PersonServiceImpl,则无法mock其实现类此时需要求助于@Captured注解

@Captured PersonService personService

1. 此时PersonService是个接口,Captured注解可以mock所有PersonService的实现类

2. 同样可以在全局变量处声明,可以在方法参数处声明

@Injectable PersonService personService

1. 跟Mocked注解一样无法实现接口实现类的mock

2. 同样可以在全局变量处声明,可以在方法参数处声明

3. 在单元测试内部自己创建一个新对象,不会被mock

@Test
public  void testInjectable(@Injectable PersonService personService){
    //自己new一个,并不受影响
    PersonService another = new PersonService();
    Assert.assertTrue(another.showAge == 10);
}

4. 多个同类型使用Injectable注解,personService1和personService2互不干扰

5. 可以注入非mock对象,如基础类型等,非mock对象除了使用@Injectable标记,还需要有明确初始值

public class SomeTest {
    @Injectable PersonService personService1;
    @Injectable PersonService personService2;
    @Injectable int someIntegralProperty = 123;

    @Test
    public void someTestMethod(@Injectable("true") boolean flag) {
       
    }
}

@Tested PersonService personService

Tested注解和Injectable注解是一对好基友,需要配合使用

如下图案例,PersonService内含SchoolService,而SchoolService的register方法被mock。

1. @Tested只能作用于具体类,而不能作用于接口

2. 和@Injectable配合使用,通过@Injectable注解对被测对象的构造函数进行改写。如果没有@Injectable,则采用原有代码进行初始化。

3. 如果@Tested的fullyInitialized=true,则被测对象的所有参数都需要被初始化。

@Service
public PersonService {
    
    @Autowired
    SchoolService schoolService;

    public void goToSchool(){
        System.out.println(schoolService.register());
    }
}

@Service
public SchoolService(){
    public Date register(){
        return new Date();
    }    
}

public TestCase{
    
    @Tested PersonService personService;
    
    @Test
    public void testGoToSchool(@Injectable SchoolService schoolService){
        new Expectation(){
            {
                schoolService.register();
                result = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse("2021-05-18 13:49:00");
            }
        };
        personService.goToSchool();
    }
}

如何拦截重写(record阶段)

Expection初探

PersonService被mock了,其showAge方法和getDefaultPerson方法的预期结果,在Expectation中被重写,类似于AOP的代理拦截重写返回。

所以单元测试中如果调用了personService.getAge,则不会返回原结果,而是返回expectation中重写的结果。

expectation可以返回单个结果,也可以通过returns(a,b,c...)返回多个结果,甚至可以中抛出异常。

@RunWith(JMockit.class)
public class MockTest {
 
    @Mocked
    private PersonService personService;
 
    @Test
    public void testInstance(){
        new Expectations(){
            {
                personService.showAge(anyInt); 
                result = -1;  // 调用personService.showAge(anyInt) 返回-1

                personService.showName(anyString);
                returns("onion","liyc290"); // personService.showName(anyString) 返回onion或liyc290,
                                            // Assert.equals(personService.showName("hello"),"onion")返回true,
                                            // Assert.equals(personService.showName("hello"),"liyc290")也返回true
 
                personService.getDefaultPerson();
                result = new RuntimeException(); // 调用personService.getDefaultPerson() 抛异常
            }
        };
        // replay
        // verify
        System.out.println(personService.showAge(5) == -1); // 返回true          
    }
}

注意:

  1. 返回值可以使用returns()函数,异常值必须要使用result。
  2. 异常值需要在PersonService中通过try...catch...捕获,否则无法通过测试。
  3. 假设在StrictExpectations中录制了n个结果,在replay阶段并不强制要求调用n次该函数,调用1次即可。

拦截重写被Mock类的静态方法

 @Test
    public void testStaticMethod(){
        new Expectations(CollectionUtils.class){{
            CollectionUtils.isEmpty((Collection<?>) any);
            result = true;
        }};
        List<Integer> list = Lists.newArrayList(1,2,3);
        Assert.assertTrue(list.size() == 3);
        Assert.assertTrue(CollectionUtils.isEmpty(list));
    }

拦截重写被mock对象的无参方法

示例如下

@Test
public void TestMethod(@Mocked final Dependency dependency) throws Exception {
    new NonStrictExpectations() {{
        dependency.intReturnMethod();
        returns(1, 2, 3);
    }};

    Dependency dependency1 = new Dependency();
    assertEquals(1, dependency1.intReturnMethod());

    Dependency dependency2 = new Dependency();
    assertEquals(2, dependency2.intReturnMethod());

    Dependency dependency3 = new Dependency();
    assertEquals(3, dependency3.intReturnMethod());
}

拦截重写被mock对象的多个方法

@RunWith(JMockit.class)
public class MockTest {

    //@Mocked 修饰,所有实例都会被mock
    @Mocked
    private PersonService personService;

    @Test
    public void testInstance(){
        new Expectations(){
            {
                personService.showAge(anyInt);
                result = -1;

                personService.getDefaultPerson();
                result = new Person("me", 4, null);
            }
        };

        //record的方法,按照给定的结果返回
       
    }
}

拦截重写被mock对象的方法中含有参数

在record和verify阶段进行方法匹配时,

  • 对于原始类型对象,数值相同即可;
  • 对于Object的子类,需要equals()返回true;
  • 对于数组,需要长度相等且每个对象equals()返回true;

除此之外,如果不关心replay时的具体参数,可以使用anyXyz或者withXyz(...)方法。

在意参数是啥,主要为了验证参数有无正确传达

参照Verify章节

不在意参数是啥,只是为了mock方法的返回值

使用"any"

@Test
public void func(@Mocked UserDao userDao)
{
   new Expectations() {{
      userDao.say(anyString, (List<?>) any);
   }};

   UserService service = new UserService();
   service.say("hello",new ArrayList()); // service层的say方法会调用dao层的say方法,然后dao层的say方法被expectation拦截重写
}
  • 任何的基本类型都有对应的anyXyz,anyString对应任意字符串。
  • any对应任意的对象,在使用时需要进行显式类型转换: (CastClass) any
  • mockit.Invocations类中有可以使用所有anyXyz
  • 使用时参数位置需要一致

使用"with"

any的限制太宽松,with可以选择特定的子集。

@Test
public void someTestMethod(@Mocked final DependencyAbc abc) {
   final DataItem item = new DataItem(...);

   new Expectations() {{
      abc.voidMethod("str", (List<?>) withNotNull());
      abc.stringReturningMethod(withSameInstance(item),      withSubstring("xyz"));
   }};

   new UnitUnderTest().doSomething(item);

   new Verifications() {{
      abc.anotherVoidMethod(withAny(1L));
   }};
}

使用"null"

null可以与任何对象匹配,好处是避免类型转换,`但是需要有一个any或者with`。

@Test
public void TestMethod(@Mocked final Dependency mock) {
    new StrictExpectations() {{

        //测试会失败,因为没有any或者with
        mock.mockMethod(2,  null);
     
        //测试通过
        mock.mockMethod(anyInt,  null);
    }};
    mock.mockMethod(new Integer(2), "hello world");
}

如何需要的是null,则应该用 withNull() 方法。

varargs

要么使用常规的参数,要么使用any/with,不能混合使用。

拦截重写被mock对象的私有变量

public class CoderServiceTest {

    @Mocked
    private CoderService coderService;

    @Test
    public void testMockCase(){
        new Expectations(coderService){{
            //mock私有变量
            Deencapsulation.setField(coderService, "desc", "coderDesc");
            result = "noWork";
        }};
        // replay
        // validate
    }

}

拦截重写被mock对象的私有方法

//模拟实例方法,注意参数不能为null,如果要传null请使用带参数类型的另一个重载方法
Deencapsulation.invoke(Object objectWithMethod, String methodName, Object... nonNullArgs)

//模拟类方法
Deencapsulation.invoke(Class<?> classWithStaticMethod, String methodName, Object... nonNullArgs)

如何Replay

略,就是调用下方法

如何Verify

一般有这样的需求,比如验证方法参数有没有正确传达,或者方法的调用顺序,调用次数。

调用次数的限制

在record和verify阶段可以使用times,minTimes,maxTimes来限制。

默认为minTimes = 1。

显式验证

对于NonStrictExpectation,可以进行verification。对于StrictExpectation则没有必要。在new Verifications(){}中的方法至少被调用一次。

验证某个方法没被调用

times = 0

验证顺序调用

普通的new Verifications(){}没有验证其中方法的调用顺序。new VerificationsInOrder(){}用来验证(相对)顺序。

验证部分顺序

使用unverifiedInvocations()方法固定不需要验证的方法的位置。

第一种场景是验证部分方法的顺序,其余方法不需要验证:

@Test
public void TestMethod(@Mocked final Dependency mock) {
    mock.mockMethod1();
    mock.mockMethod2();
    mock.mockMethod3();
    mock.mockMethod4();

    new VerificationsInOrder(){
        {
            // 下面的代码会失败:
            // Unexpected invocation of: Dependency#mockMethod2()
            // 如果两个方法相连,则其在replay中也必须直接相连
            // unverifiedInvocations();
            // mock.mockMethod1();
            // mock.mockMethod4();
            
            // 成功
            mock.mockMethod1();
            unverifiedInvocations();
            mock.mockMethod4();
        }
    };
}

第二种场景是关心部分方法顺序,另一些方法也需要验证,但是不关心顺序。这时需要两个Verification块:

@Test
public void TestMethod(@Mocked final Dependency mock) {
    mock.mockMethod1();
    mock.mockMethod2();
    mock.mockMethod3();
    mock.mockMethod4();

    new VerificationsInOrder(){{
            mock.mockMethod1();
            unverifiedInvocations();
            mock.mockMethod4();
    }};

    new Verifications(){{
        mock.mockMethod3();
        mock.mockMethod2();
    }};
}

多个verification块时,其相对顺序会引起比较诡异的事:

@Test
public void TestMethod(@Mocked final Dependency mock) {
    mock.mockMethod1();
    mock.mockMethod2();
    mock.mockMethod3();
    mock.mockMethod4();
    
    //下面的代码会失败
    //MissingInvocation: Missing invocation of:Dependency#mockMethod2()
    //颠倒一下两个verification的顺序则会通过
    //原因似乎是Verifications会将验证过的方法删除

    new Verifications(){{
        mock.mockMethod3();
        mock.mockMethod2();
    }};

    new VerificationsInOrder(){{
        mock.mockMethod1();
        mock.mockMethod2();
        mock.mockMethod4();
    }};
}

full verification

new FullVerifications() {...}可以保证replay阶段调用的所有方法在verify代码块中都有相应的匹配,顺序可以不一致。

full verification in order

使用new FullVerificationsInOrder()

限制full verification的目标类型

默认使用full verification时,所有mock类的所有调用都必须显式验证。如果需要限定验证的类或者实例,使用FullVerifications(xxx.class)或者FullVerifications(mockObject)。

验证没有调用发生

使用空的FullVerifications(xxx.class)或者FullVerifications(mockObject)可以验证在指定类/实例上没有调用方法。但是如果Expectation中有minTimes和times的方法会被正常验证。

在verification中捕获调用参数

单次调用捕获

使用withCapture()捕获最后一次调用的参数。

java
@Test
public void capturingArgumentsFromSingleInvocation(@Mocked final Collaborator mock)
{
   new Collaborator().doSomething(0.5, new int[2], "test");

   new Verifications() {{
      double d;
      String s;
      mock.doSomething(d = withCapture(), null, s = withCapture());

      assertTrue(d > 0.0);
      assertTrue(s.length() > 1);
   }};
}

多次调用捕获

使用`withCapture(List)`捕获所有参数。

@Test
public void capturingArgumentsFromMultipleInvocations(@Mocked final Collaborator mock)
{
   mock.doSomething(dataObject1);
   mock.doSomething(dataObject2);

   new Verifications() {{
      List<DataObject> dataObjects = new ArrayList<>();
      mock.doSomething(withCapture(dataObjects));

      assertEquals(2, dataObjects.size());
      DataObject data1 = dataObjects.get(0);
      DataObject data2 = dataObjects.get(1);
      // Perform arbitrary assertions on data1 and data2.
   }};
}

捕获新实例

使用withCapture(new XX())

@Test
public void capturingNewInstances(@Mocked Person mockedPerson) {
   new Person("Paul", 10);
   new Person("Mary", 15);
   new Person("Joe", 20);

   new Verifications() {{
      List<Person> personsInstantiated = withCapture(new Person(anyString, anyInt));
   }};
}

使用Delegate在Expectation中定制result

使用场景:在Expectation中需要根据replay时的参数值决定返回值。

原理:JMockit拦截调用,转交给Delegate处理。

@Test
public void delegatingInvocationsToACustomDelegate(@Mocked final DependencyAbc anyAbc){
   new Expectations() {{
      anyAbc.intReturningMethod(anyInt, null);
      result = new Delegate() {
         int aDelegateMethod(int i, String s)
         {
            return i == 1 ? i : s.length();
         }
      };
   }};

   // Calls to "intReturningMethod(int, String)" will execute the delegate method above.
   new UnitUnderTest().doSomething();
}
  • delegate方法的参数应该与原始方法一致,返回值需要兼容或者为异常。
  • 可以delegate构造器,这时返回值设置为空。
  • delegate参数中可以有一个 Invocation对象,从而获得调用者的引用。

级联mock

出现 obj1.getObj2(...).getYetAnotherObj().doSomething(...)时可能需要mock多个对象。对于一个mock对象:

  • Expectation中进行了record,则会返回record的result;
  • 如果没有record,JMockit会自动创建一个返回被注解@Injectable的子对象
public class Dependency {

    public CascadeDependency getCascadeDependency() {
        //JMockit会拦截这个方法,返回一个非null对象
        return null;
    }
    
    public CascadeDependency getAnotherCascadeDependency() {
        //JMockit会拦截这个方法,返回一个非null对象
        return null;
    }

    public String getString() {
        //仍旧返回null
        return null;
    }

    public Object getObject() {
        //仍旧返回null
        return null;
    }

    public List<Object> getList() {
        //返回empty集合
        return null;
    }
}
@Test
public void TestMethod(@Mocked Dependency dependency) {

    CascadeDependency first = dependency.getCascadeDependency();
    CascadeDependency second = dependency.getCascadeDependency();
    
    //调用另一个方法
    CascadeDependency third = dependency.getAnotherCascadeDependency();
    
    //所有都不会为null
    assertNotNull(first);
    assertNotNull(second);
    assertNotNull(third);
    
    //相同方法返回JMockit创建的同一个对象
    assertSame(first, second);
    
    //不同方法返回JMockit创建的同一个对象
    assertNotSame(first, third);
    
    //String返回null
    assertNull(dependency.getString());

    //Object返回null
    assertNull(dependency.getObject());
    
    //返回empty集合
    assertNotNull(dependency.getList());
    assertEquals(0, dependency.getList().size());
}
@Test
public void TestMethod(@Mocked Dependency dependency, 
                       @Mocked CascadeDependency cascadeDependency) {

    CascadeDependency first = dependency.getCascadeDependency();
    CascadeDependency second = dependency.getAnotherCascadeDependency();
    
    //因为子对象也@Mocked,所以会返回同一个对象
    assertSame(first, second);
}

JMockit返回的非空对象实际上进行了@Injectable标识,所以:

@Test
public void TestMethod(@Mocked Dependency dependency) {

    //虽然CascadeDependency没有出现在参数中,
    //但是JMockit对其进行了@Injectable
    //而由于没有在Expectation中record mockMethod的result,所以返回空
    assertNull(dependency.getCascadeDependency().mockMethod());

    //不影响CascadeDependency的其他实例
    assertNotNull(new CascadeDependency().mockMethod());
}

也可以在Expectation中使用result指定返回对象,从而禁止JMockit自动生成。

@Test
public void TestMethod(@Mocked final Dependency dependency) {

    //在Expectation中指定了返回结果,因此JMockit不会生成CascadeDependency
    new NonStrictExpectations(){{
        dependency.getCascadeDependency();
        result = null;
        result = new CascadeDependency();
    }};
    
    //第一次返回null
    assertNull(dependency.getCascadeDependency());
    
    //第二次返回新对象
    assertNotNull(dependency.getCascadeDependency().mockMethod());
}

mock级联调用特别适合static factory,getCurrentInstance()永远不会返回null。

@Test
public void TestMethod(@Mocked final Dependency dependency) {
    assertSame(dependency, dependency.getCurrentInstance());
}

在Builder模式中也很方便验证,

@Test
public void createOSProcessToCopyTempFiles(@Mocked final ProcessBuilder pb) throws Exception{
    Process copy = new ProcessBuilder().command(cmdLine).directory(wrkDir).inheritIO().start();

   new Verifications() {{ pb.command(withSubstring("copy")).start(); }};
}

部分mock

有时候只需要mock部分方法,这时候可以用new Expectations(object),object可以是实例,也可以是class对象。在replay阶段,如果在Expectation中没有进行record,则会调用原有代码。

@Test
public void partiallyMockingASingleInstance() {
  final Collaborator collaborator = new Collaborator(2);

  new Expectations(collaborator) {{
     collaborator.getValue(); result = 123;

     // 静态方法也可以
     Collaborator.doSomething(anyBoolean, "test");
  }};

  // Mocked:
  assertEquals(123, collaborator.getValue());
  Collaborator.doSomething(true, "test");

  // Not mocked:
  assertEquals(45, new Collaborator(45).getValue());
}
  • Note:上面的代码中没有出现@Mocked注解

没有record的方法也可以verify,

@Test
public void partiallyMockingA() {
    final Collaborator collaborator = new Collaborator(123);

    new Expectations(collaborator) {};
  
    int value = collaborator.getValue(); 
    collaborator.simpleOperation(45, "testing", new Date());

    // 没有record也可以verify
    new Verifications() {{ 
        c1.simpleOperation(anyInt, anyString, (Date) any);
    }};
}

另一种实现部分mock的方法:同时标注@Tested和@Mocked。

mock接口

有些实现类是匿名的:

public interface Service { int doSomething(); }
public final class TestedUnit {

    private final Service service = new Service() { 
        public int doSomething() { return 2; } 
    };

    public int businessOperation() {
        return service.doSomething();
    }
}

使用@Capturing标注基类/接口,所有实现类会被mock:

@Capturing Service anyService;

@Test
public void mockingImplementationClassesFromAGivenBaseType() {
    new Expectations() {{ 
        anyService.doSomething(); 
        returns(3); 
    }};

    int result = new TestedUnit().businessOperation();
    assertEquals(3, result);
}

@Capturing是@Mock的增强版,有一个可选参数maxInstances用于捕获前面指定数量的对象,其默认值为Integer.MAX_VALUE。

@Test
public void TestMethod(@Capturing(maxInstances = 2) final Dependency dependency1,
                       @Capturing(maxInstances = 2) final Dependency dependency2,
                       @Capturing final Dependency remain) {
    new NonStrictExpectations() {{
        dependency1.getValue();
        result = 1;
        dependency2.getValue();
        result = 2;
        remain.getValue();
        result = 3;
    }};
    assertEquals(1, new Dependency().getValue());
    assertEquals(1, new Dependency().getValue());
    assertEquals(2, new Dependency().getValue());
    assertEquals(2, new Dependency().getValue());
    assertEquals(3, new Dependency().getValue());
}

上面的@Capturing是出现在参数列表中的,如果是作为field声明的,maxInstances会失效,@Capturing退化为@Mock。

自动注入被测试类

@Tested标注被测试类,在运行测试方法时,如果该实例仍然为null,JMockit会自动组装相关mock对象,进行初始化。在组装被测试类过程中,相关mock对象必须使用@Injectable标记,非mock对象除了使用@Injectable标记,还需要有明确初始值。

public class SomeTest {
    @Tested CodeUnderTest tested;
    @Injectable Dependency dep1;
    @Injectable AnotherDependency dep2;
    @Injectable int someIntegralProperty = 123;

    @Test
    public void someTestMethod(@Injectable("true") boolean flag) {
        tested.exerciseCodeUnderTest();
    }
}

注入先根据类型匹配,再根据参数名称匹配。

----------------------------------------------------------MockUp API------------------------------------------------

MockUp

前面介绍的是基于行为的测试,利用的是Expectation API,接下来介绍基于状态的测试,也就是mock-up API。

mock-up类是继承mockit.MockUp<T>的类,mock方法是@Mock的方法,T是被mock的类。例如:

public class FakeClass extends MockUp<Dependency> {

    @Mock
    //mock构造器
    public void $init(String name) {
      assertNotNull(name);
    }

    @Mock
    public void mockMethod() {}
}

直接用匿名内部类MockUp

进行测试时,可以在@BeforeClass,@Before,@Test方法中使用new来初始化mock-up类,@Mock方法就会代替真实方法。如果被mock的类中没有相应的方法,就会抛出异常。没有被@Mock注解的方法会执行原来的方法。

为了方便,可以直接初始化匿名内部类:

@Test
public void testMethod() throws Exception() {
    new MockUp<Dependency>() {};

    //CodeUnderTest;
}

伪造接口实现类

直接初始化MockUp类就可以完成打桩,如果希望伪造某个接口的实现类,可以使用getInstance()方法:

@Test
public void faking() throws Exception {
    CallbackHandler callbackHandler = new MockUp<CallbackHandler>() {
        @Mock
        void handle() {
        //fake implementation
        }
    }.getMockInstance();
    canllbackHandler.handle();
}

假设只知道某个接口类型,而不知道其实现类的具体类型(比如实现类是匿名类,或者根本未知),这时候使用泛型通配符可以为这些类设定返回类型:

@Test
public <T extends Service> void faking() { 
    new MockUp<T>() {
        @Mock int doSomething() { return 7; } 
    }; 
    //业务代码中所有 Service类的子类都返回7
}

伪造类初始化过程

如果某个类在需要在static块中完成一些初始化工作,而在测试时希望忽略掉这些初始化,就需要使用$clinit方法来伪造类初始化过程。

public class Dependency {    

    //static赋值会被忽略
    public static String staticfield = "staticField will not be initialized";

    //final可以赋值
    public static final String finalfield = "finalField will be initialized";    

    //static语句块不会执行
    static {
        staticField = "staticField will not be initialized";    
    }
}
@Testpublic void TestMethod() {
    new MockUp<Dependency>() {
        @Mock
        void $clinit(){}
    };
    assertNull(new Dependency().staticField);
    assertNull(new Dependency().finalField);
}

调用者的上下文

在Fake类的方法中,可以设置一个Invocation类对象,在该方法被调用时,JMockit会为这个对象传值。这种机制的意义是Fake类可以接触到真实类。因此,Fake类的方法可以得到调用参数,调用次数等信息。

@Test
public void invocation() throws Exception {

   final Subject testSubject = new Subject();

   new MockUp<LoginContext>() {
      @Mock
      void $init(Invocation invocation, String name, Subject subject)
      {
         assertNotNull(name);
         assertSame(testSubject, subject);

         // Gets the invoked instance.
         LoginContext loginContext = invocation.getInvokedInstance();

         // Verifies that this is the first invocation.
         assertEquals(1, invocation.getInvocationCount());
      }

      @Mock
      void login(Invocation invocation)
      {
         // Gets the invoked instance.
         LoginContext loginContext = invocation.getInvokedInstance();

         // getSubject() returns null until the subject is authenticated.
         assertNull(loginContext.getSubject());
      }

      @Mock
      void logout(Invocation invocation)
      {
         // Gets the invoked instance.
         LoginContext loginContext = invocation.getInvokedInstance();

         assertSame(testSubject, loginContext.getSubject());
      }
   };

   LoginContext theFakedInstance = new LoginContext("test", testSubject);
   theFakedInstance.login();
   theFakedInstance.logout();
}

执行原有代码

这个功能和装饰器或者拦截器相似,JMockit拦截调用,交给fake类,fake类执行某些功能之后再回调原来的代码。

@Mock
void login(Invocation inv) {    
    inv.proceed(); 
}

多个类公用MockUp

@RunWith(Suite.class)
@Suite.SuiteClasses({MyFirstTest.class, MySecondTest.class})
public final class TestSuite{ 
    @BeforeClass
    public static void applyGlobalMockUps() { 
        new LoggingMocks(); 
        new MockUp<SomeClass>() { 
            @Mock 
            someMethod() {} 
        }; 
    }
}

伪造所有方法

@Mock
public Object $advice(Invocation invocation) {
    invocation.proceed();
}

使用$advice()方法,使得对所有方法的调用都执行该方法。

Jmockit Coverage

  <dependency>
            <groupId>org.jmockit</groupId>
            <artifactId>jmockit-coverage</artifactId>
            <version>1.13</version>
            <scope>test</scope>
        </dependency>


 <plugin>
               <artifactId>maven-surefire-plugin</artifactId>
               <version>2.21.0</version>
               <configuration>
                   <testFailureIgnore>true</testFailureIgnore>
                   <argLine>
                       -javaagent:${settings.localRepository}/org/jmockit/jmockit/${jmockit.version}/jmockit-${jmockit.version}.jar=coverage
                   </argLine>
                   <systemPropertyVariables>
                       <disableXmlReport>true</disableXmlReport>
                       <coverage-output>html</coverage-output>
                       <coverage-metrics>all</coverage-metrics>
                       <coverage-classes>loaded</coverage-classes>
                       <coverage-srcDirs>src</coverage-srcDirs>
                       <coverage-check>80</coverage-check>
                       <coverage-outputDir>${project.build.directory}/codecoverage-output</coverage-outputDir>
                   </systemPropertyVariables>
               </configuration>
           </plugin>

  • 2
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值