JMockit简单使用(二)

时间:2025-04-12 07:15:35
       JMockit使用时,建议使用Expectations{}块来录制行为,这样mock对象在运行时必须 依据Expectations块中定义的顺序依次调用方法,不能多调用也不能少调用,可以省略掉Verifications块。一旦多调用或则少调用,那么测试就不会通过,这样可以清晰的把握单元测试每一步、每一次执行的过程。
       但是,测试的方法中如果存在线程的调用来执行录制行为,很多情况用Expectations{}块测试都不会通过,而使用NonStrictExpectations{}块来录制就不会有问题。

示例:
测试目标:
package ;

import ;
import ;

class OtherVO {
    private String value;

    public String getValue() {
        return value;
    }

    public void setValue(String value) {
         = value;
    }
}

public class Work {
    private OtherVO otherVO;
    private ExecutorService executor = (4);

    public void task() {
        (new Runnable() {
            public void run() {
                ();
            }
        });
    }
}
测试文件:
package ;

import ;
import ;
import ;
import ;
import .;

import ;
import ;

@RunWith()
public class WorkTest {
    @Tested
    @Mocked
    Work work;

    @Test
    public void testTask() {
        final OtherVO otherVO = new OtherVO();
        new Expectations() { // mock OtherVO的行为
            {
                ();
                result = "zero";
            }
        };
        new Expectations() {
            {
                (work, "otherVO", otherVO);
            }
        };
        // --- 通过以上方式可以将mock出来的otherVO设置到work中,当然可以采取其它简便方式
        
        ();
    }
}
运行测试,会发现测试不通过,会抛出一下异常:
: Missing 1 invocation to:
#getValue()
   on mock instance: @3419866c
	at ..(:50)
	at (:38)
	at (:459)
	at (:675)
	at (:382)
	at (:192)
Caused by: Missing invocations
	at ()
	at $1.<init>(:23)
	at (:21)
	at .invoke0(Native Method)
	at (:497)
	... 6 more
明明调用了(),task()里会调用getValue()方法,但是怎么会报Missing invocations错误(少调了OtherVO#getValue()方法)。为什么呢?原因在于线程的调用,()只是通知线程去执行runnable,runnable中任务的执行需要JVM去调度,至于什么时候去执行,可能发生在整个testTask()结束后,而Expectations{}检测到录制行为在testTask()结束前依然没被回放,因此就会报错。

怎么解决呢?
       在()调用后sleep()一段时间,以等runnable中任务被执行了再结束testTask()不就可以了么。这也是一个办法。但最好的解决方式是mock executor的execute()方法,让它不执行(),而是执行(),如下:
@RunWith()
public class WorkTest {
    @Tested
    @Mocked
    Work work;

    private ExecutorService executor;

    @Before
    public void initThread() {
        executor = new MockUp<ThreadPoolExecutor>() {
            @Mock
            public void execute(Runnable command) {
                ();
            }
        }.getMockInstance();
    }

    @Test
    public void testTask() {
        final OtherVO otherVO = new OtherVO();
        new Expectations() { // mock OtherVO的行为
            {
                ();
                result = "zero";
            }
        };
        new Expectations() {
            {
                (work, "otherVO", otherVO);
            }
        };
        // --- 通过以上方式可以将mock出来的otherVO设置到work中,当然可以采取其它简便方式

        new Expectations() {
            {
                (work, "executor", executor);
                // --- 将mock出来executor设置到work中
            }
        };

        ();
    }
}
另一种简单方式:
@RunWith()
public class WorkTest {
    @Tested
    @Mocked
    Work work;

    @Test
    public void testTask() {
        final OtherVO otherVO = new OtherVO();
        new Expectations() { // mock OtherVO的行为
            {
                ();
                result = "zero";
            }
        };
        new Expectations() {
            {
                (work, "otherVO", otherVO);
            }
        };
        // --- 通过以上方式可以将mock出来的otherVO设置到work中,当然可以采取其它简便方式

        new MockUp<ThreadPoolExecutor>() {
            @Mock
            public void execute(Runnable command) {
                ();
            }
        };

        ();
    }
}