方法包括构造方法是 mock 的目标。Mocking 提供了把被测代码和它的依赖隔离的机制。我们通过标注测试类的属性和测试方法的参数来提供 mock 对象。mock 对象的类型可以是任意的引用类型:interface, class (包括 final), annotation 和 enum 。
缺省的,类的所有方法包括构造方法都会被mock, 不管它有private, final, static 等任何修饰符。 同时,类的所有除了Object 之外的所有祖先类也被mock。被mock 的方法调用都会被重定向给 JMockit 处理。
下面的代码演示了使用mock field 和参数的典型方式:
// "Dependency" is mocked for all tests in this test class. // The "mockInstance" field holds a mocked instance automatically created for use in each test. @Mocked Dependency mockInstance; @Test public void doBusinessOperationXyz(@Mocked final AnotherDependency anotherMock) { ... new Expectations() {{ // an "expectation block" ... // Record an expectation, with a given value to be returned: mockInstance.mockedMethod(...); result = 123; ... }}; ... // Call the code under test. ... new Verifications() {{ // a "verification block" // Verifies an expected invocation: anotherMock.save(any); times = 1; }}; ... }
上例中mock的参数是在JMockit生成之后,由Junit或TestNG的runner传入的。(显然,这就是为什么JMockit 依赖JUnit或TestNG 的原因)
有三种mock标注:
使用 2 或 3 , 意味着隐含的使用了1 。
与其他的mock框架不同, @Mocked 和 @Capturing 缺省mock的是类。这意味着,一旦你这样" @Mocked SomeClass ins1" , 即使你调用构造方法new一个对象 ins2,ins2也是mock对象。
与其他 mock 框架的不同,定制交互行为只需要在expectation 块内直接调用mock对象的方法,无需特殊的 API.
任何测试可以被划分到3各阶段:
@Test public void someTestMethod() { // 1. Preparation: whatever is required before the code under test can be exercised. ... // 2. The code under test is exercised, usually by calling a public method. ... // 3. Verification: whatever needs to be checked to make sure the code exercised by // the test did its job. ... }
这3个阶段也可以称为 Arrange, Act, Assert 。
当然,Expectations 块或 Verifacations 块也有多个也可以没有。
Expectations 块中记录的调用是常规的, 意味着最低期望。最低期望哪些方法会在 replay 阶段调用,调用的最少次数。但不关心顺序。
StrictExpectation 意味着必须。 replay 阶段调用了哪几个方法,调用的次数和顺序,都必须和 record 阶段一样,否则测试fail (unexpected invocation)。
NonStrictExpectations 则无所谓,也就是相当于不做验证,只是用来指定result。建议仅被用于setup(@Before )阶段。
以上三者的区别意味着对于 StrictExpectation 不需要 Verifications 块,而NonStrictExpectations 需要。 ??终于明白它为啥叫 Expectations 了??
在Expectations 块内,对于 non-void 的方法,紧跟在方法调用之后给 result 赋值来指定返回值。
可以通过给 result 赋 Throwable 的实例来让方法抛出异常,这也适用与构造方法。
对与同一个方法连续调用而返回不同的值或异常,有3种选择可以办到:1,给result 赋值一个数组或list ; 2, 调用 results(Object ...) ; 3, 在同一行连续 对result 赋值多次。
看下面这个示例:
首先是被测的类:
public class UnitUnderTest { (1)private final DependencyAbc abc = new DependencyAbc(); public void doSomething() { (2) int n = abc.intReturningMethod(); for (int i = 0; i < n; i++) { String s; try { (3) s = abc.stringReturningMethod(); } catch (SomeCheckedException e) { // somehow handle the exception } // do some other stuff } } }
其中 3 个数字是三个测试点,考虑要如何 expect 构造方法及对(3)的连续调用。
测试如下:
@Test public void doSomethingHandlesSomeCheckedException(@Mocked final DependencyAbc abc) throws Exception { new Expectations() {{ (1) new DependencyAbc(); (2) abc.intReturningMethod(); result = 3; (3) abc.stringReturningMethod(); returns("str1", "str2"); result = new SomeCheckedException(); }}; new UnitUnderTest().doSomething(); }
@Mocked 会作用与类的所有实例上。 但有时我们需要verify在指定实例上的方法调用,或者我们也同时需要mocked和未 mocked 的实例。@Injectable 用于满足这个要求。
正因为@Injectable 只作用于指定的实例,所有不能mock构造方法和static的方法,也不影响祖先类。
示例:
被测类型:
public final class ConcatenatingInputStream extends InputStream { private final Queue<InputStream> sequentialInputs; private InputStream currentInput; public ConcatenatingInputStream(InputStream... sequentialInputs) { this.sequentialInputs = new LinkedList<InputStream>(Arrays.asList(sequentialInputs)); currentInput = this.sequentialInputs.poll(); } @Override public int read() throws IOException { if (currentInput == null) return -1; int nextByte = currentInput.read(); if (nextByte >= 0) { return nextByte; } currentInput = sequentialInputs.poll(); return read(); } }
测试:
@Test public void concatenateInputStreams( @Injectable final InputStream input1, @Injectable final InputStream input2) throws Exception { new Expectations() {{ input1.read(); returns(1, 2, -1); input2.read(); returns(3, -1); }}; InputStream concatenatedInput = new ConcatenatingInputStream(input1, input2); byte[] buf = new byte[3]; concatenatedInput.read(buf); assertArrayEquals(new byte[] {1, 2, 3}, buf); }
注意这里必须使用@Injectable 的原因是,我们不想影响基类 InputStream 的 read() 方法, 所以不能用 @Mocked。
onInstance(m)
constraint我们使用 @Mocked 或 @Capturing 时,也可以在record 阶段通过 onInstance(mocked) 方法来限制作用范围仅在指定的实例上。
@Test public void matchOnMockInstance(@Mocked final Collaborator mock) { new Expectations() {{ onInstance(mock).getValue(); result = 12; }}; // Exercise code under test with mocked instance passed from the test: int result = mock.getValue(); assertEquals(12, result); // If another instance is created inside code under test... Collaborator another = new Collaborator(); // ...we won‘t get the recorded result, but the default one: assertEquals(0, another.getValue()); }
实际上,如果 @Mocked 或 @Capturing 作用与多个相同的类型变量时,JMockit 会自动推断 record 是仅仅 match 指定的实例的。也就是说,不需要 onInstance() 方法。
不同的构造方法产生的实例,record 不同的行为,可以采用以下两种方式之一:
方式一:
@Test public void newCollaboratorsWithDifferentBehaviors(@Mocked Collaborator anyCollaborator) { // Record different behaviors for each set of instances: new Expectations() {{ // One set, instances created with "a value": Collaborator col1 = new Collaborator("a value"); col1.doSomething(anyInt); result = 123; // Another set, instances created with "another value": Collaborator col2 = new Collaborator("another value"); col2.doSomething(anyInt); result = new InvalidStateException(); }}; // Code under test: new Collaborator("a value").doSomething(5); // will return 123 ... new Collaborator("another value").doSomething(0); // will throw the exception ... }
注意这里,anyCollaborator 参数并没有用到,但这里 @Mocked 使得类型 Collaborator 可以定制 Expectations。
方式二:
@Test public void newCollaboratorsWithDifferentBehaviors( @Mocked final Collaborator col1, @Mocked final Collaborator col2) { new Expectations() {{ // Map separate sets of future instances to separate mock parameters: new Collaborator("a value"); result = col1; new Collaborator("another value"); result = col2; // Record different behaviors for each set of instances: col1.doSomething(anyInt); result = 123; col2.doSomething(anyInt); result = new InvalidStateException(); }}; // Code under test: new Collaborator("a value").doSomething(5); // will return 123 ... new Collaborator("another value").doSomething(0); // will throw the exception ... }
方式二看起来与record非构造方法的风格一致。而且不影响未mock的实例。
对于 record 和 verify , 很多时候我们并不希望精确的匹配参数值,这时就需要使用弹性匹配。
弹性匹配通过两种方式表达,anyXxx 或 withXx() 方法,他们都是 Expectations 和 Verifacations 的基类 mockit.Invocations 的成员,所以对所有的Expectation 和 Verifaction 可用。
三类:原始及其包装类型,anyString , 还有一个 any 。
@Test public void someTestMethod(@Mocked final DependencyAbc abc) { final DataItem item = new DataItem(...); new Expectations() {{ // Will match "voidMethod(String, List)" invocations where the first argument is // any string and the second any list. abc.voidMethod(anyString, (List<?>) any); }}; new UnitUnderTest().doSomething(item); new Verifications() {{ // Matches invocations to the specified method with any value of type long or Long. abc.anotherVoidMethod(anyLong); }}; }
注意, 1, 怎样表达任意的 List ; 2, void 方法没有必要放在Expectations 块中吧。
相比较与anyXx, withXx() 允许传入参数,这就使得他们更灵活一些。 除了预定义的 withXx() 之外,还可以通过 with(Delegate ) 和 withArgThat(org.hamcrest.Matcher )自定义匹配。
null
value to match any object reference可以使用 null 去替代 any 或 withAny() 来配置任意值,但仅限于至少一个参数使用了 anyXx 或 withXx() ,否则它精确匹配 null 。 另外可用 withNull() 去匹配 null 值。
如果参数的个数无所谓的话,可以使用 "(Object[]) any" 匹配;
变长参数不能使用值和matcher的组合。
在Expectations 或 Verifacations 块内都可以指定;指定在对应的方法之后。
通过三个属性指定: times, minTimes 和 maxTimes 。 每个属性只能使用一次。
times=0 或 maxTimes=0 意味着一旦目标方法被调用测试就fail。
如前文,对 mock方法调用的次数的验证可以在 Expectations 块中指定,也可以在Verifacations 块中指定。其语法规则和 Expectations 中是一样的。
因为 StrictExpectations 已经严格的表达了验证的要求,不能多也不能少,所以不能再有 Verifacations 块。
对与 regular的 Expectations , 因为它 record 的是最低要求,如果还有更多要求则需要在 Verifications 阶段补充。比如,不允许一个方法调用过,可以通过 times=0 指定。
如果需要验证方法 replay 的顺序,则使用 VerificationsInOrder 。
@Test public void verifyingExpectationsInOrder(@Mocked final DependencyAbc abc) { // Somewhere inside the tested code: abc.aMethod(); abc.doSomething("blah", 123); abc.anotherMethod(5); ... new VerificationsInOrder() {{ // The order of these invocations must be the same as the order // of occurrence during replay of the matching invocations. abc.aMethod(); abc.anotherMethod(anyInt); }}; }
注意这里, abc.doSomething() 没有验证,所以它是否出现及出现的顺序都无关紧要。
可以通过 unverifiedInvocations() 隔离方法之间顺序的相关性。
@Mocked DependencyAbc abc; @Mocked AnotherDependency xyz; @Test public void verifyingTheOrderOfSomeExpectationsRelativeToAllOthers() { new UnitUnderTest().doSomething(); new VerificationsInOrder() {{ abc.methodThatNeedsToExecuteFirst(); unverifiedInvocations(); // Invocations not verified must come here... xyz.method1(); abc.method2(); unverifiedInvocations(); // ... and/or here. xyz.methodThatNeedsToExecuteLast(); }}; }
这里会验证 1,abc.methodThtNeedsToExecuteFirst() 是第一个调用 2,xyz.methodThatNeedsToExecuteLast() 是最好一个调用; 3,xyz.method1(); abc.method2() 先后调用;但在 unverifiedInvocations() 地方可能还有更多的方法调用,但无所谓。
有时,我们关心某几个方法的调用顺序,但剩下的几个方法只验证调用了,不要求顺序,这时可通过两个验证块来表达:
1 @Test 2 public void verifyFirstAndLastCallsWithOthersInBetweenInAnyOrder() 3 { 4 // Invocations that occur while exercising the code under test: 5 mock.prepare(); 6 mock.setSomethingElse("anotherValue"); 7 mock.setSomething(123); 8 mock.notifyBeforeSave(); 9 mock.save(); 10 11 new VerificationsInOrder() {{ 12 mock.prepare(); // first expected call 13 unverifiedInvocations(); // others at this point 14 mock.notifyBeforeSave(); // just before last 15 mock.save(); times = 1; // last expected call 16 }}; 17 18 // Unordered verification of the invocations previously left unverified. 19 // Could be ordered, but then it would be simpler to just include these invocations 20 // in the previous block, at the place where "unverifiedInvocations()" is called. 21 new Verifications() {{ 22 mock.setSomething(123); 23 mock.setSomethingElse(anyString); 24 }}; 25 }
上面即使去掉第13行,测试也通过。但却不能验证 6,7 行出现在5和8之间。
类似 StrictExpectations, 严格验证被调用的方法有哪些个,不能多也不能少。
1 @Test 2 public void verifyAllInvocations(@Mocked final Dependency mock) 3 { 4 // Code under test included here for easy reference: 5 mock.setSomething(123); 6 mock.setSomethingElse("anotherValue"); 7 mock.setSomething(45); 8 mock.save(); 9 10 new FullVerifications() {{ 11 // Verifications here are unordered, so the following invocations could be in any order. 12 mock.setSomething(anyInt); // verifies two actual invocations 13 mock.setSomethingElse(anyString); 14 mock.save(); // if this verification (or any other above) is removed the test will fail 15 }}; 16 }
注意如果最小次数验证在Expectations 中指定,则在 FullExpections 中无需再指定。 这个验证始终在测试结束前执行。
@Test public void verifyAllInvocationsInOrder(@Mocked final Dependency mock) { // Code under test included here for easy reference: mock.setSomething(123); mock.setSomethingElse("anotherValue"); mock.setSomething(45); mock.save(); new FullVerificationsInOrder() {{ mock.setSomething(anyInt); mock.setSomethingElse(anyString); mock.setSomething(anyInt); mock.save(); }}; }
这里, mock.setSomething(anyInt) 必须指定两次。
@Test public void verifyAllInvocationsToOnlyOneOfTwoMockedTypes( @Mocked final Dependency mock1, @Mocked AnotherDependency mock2) { // Inside code under test: mock1.prepare(); mock1.setSomething(123); mock2.doSomething(); mock1.editABunchMoreStuff(); mock1.save(); new FullVerifications(mock1) {{ mock1.prepare(); mock1.setSomething(anyInt); mock1.editABunchMoreStuff(); mock1.save(); times = 1; }}; }
也就是传递 mock objects 或 Classes 做参数给 FullVerifications(...) , 即只关心指定的 mock 对象或类型。
待续
待续
原文:http://www.cnblogs.com/yoogo/p/JMockit.html