好了,来到最后一个API,个人认为是十分十分有意思的API,叫做Jmockit Mockups API,前面说到过,这是一个state-oriented mocking API,这几天写了前两章后,有点小感悟,state是状态的意思,Expectations API和Verifications API,都是可以很灵活的匹配我们传入的参数,以得到之前录制的结果,而Mockups API可以完成的工作是,使用它,我们可以不关心是谁用它,就像前奏时说的,我们可以复写调用的依赖,根据不同的参数,设置返回不同的返回值,也就是不同的状态进行改变,这也是他有意思的地方。看看例子,会更容易明白。
package jmockit.tutorial.domain;
import java.util.*;
import org.apache.commons.mail.*;
import jmockit.tutorial.persistence.*;
import static org.junit.Assert.*;
import org.junit.*;
import mockit.*;
public final class MyBusinessService_MockupsAPI_Test
{
public static final class MockDatabase extends MockUp<Database>
{
@Mock
public void $clinit() { /* do nothing */ }
@Mock(invocations = 1)
(1) public List<EntityX> find(String ql, Object... args)
{
assertNotNull(ql);
assertTrue(args.length > 0);
return Arrays.asList(new EntityX(1, "AX5", "someone@somewhere.com"));
}
@Mock(maxInvocations = 1)
(2) public void persist(Object o) { assertNotNull(o); }
}
@BeforeClass
public static void mockUpPersistenceFacade()
{
// Applies the mock class by invoking its constructor:
new MockDatabase();
}
final EntityX data = new EntityX(5, "abc", "5453-1");
@Test
public void doBusinessOperationXyz() throws Exception
{
// Defines and applies a mock class in one operation:
new MockUp<Email>() {
@Mock(invocations = 1)
Email addTo(Invocation inv, String email)
{
assertEquals(data.getCustomerEmail(), email);
return inv.getInvokedInstance();
}
@Mock(invocations = 1)
(4) String send() { return ""; }
};
new MyBusinessService().doBusinessOperationXyz(data);
}
@Test(expected = EmailException.class)
public void doBusinessOperationXyzWithInvalidEmailAddress() throws Exception
{
new MockUp<Email>() {
@Mock
(3) Email addTo(String email) throws EmailException
{
assertNotNull(email);
throw new EmailException();
}
@Mock(invocations = 0)
String send() { return null; }
};
new MyBusinessService().doBusinessOperationXyz(data);
}
}
天哪,不用仔细看例子,都发现了内容比前两章多了不止一点点。这同样这第三个API有趣的地方,来仔细看看。
在这里,我们通过修改了感兴趣(其实也就是我们被测类的依赖)的类的方法,而不是使用通过record expectations或者verify expectation mock了的方法。注意,这里使用的词语,是修改,这就好像,我们调用的是自己复写的代码,而不是原来的产品代码,太神奇了,是不是。必须说明的是,这些修改的方法必须和真是的方法必须使用相同的signature,并且添加上@Mock,而如果含有static block的话,还必须使用像例子中的“$clinit”,否则就会出错,这个在后面会有介绍。
@Mock
public void $clinit() { /* do nothing */ }
这些需要mock的依赖可以定义在一个独立的类里面,像MockDatabase,或者定义在一个测试方法里面的匿名内部类,两个测试方面里面都有。不管是哪一种,都必须继承MockUp<T>这个基础类,并且将T替换为需要mock的类型(这个类型可是interface等等)。
示例中的两个test cases都重复的使用了一个mock class——MockDatabase,在一个@BeforeClass方法中执行了,这样在整个测试类的生命周期中都会起作用(记住,这个是必须的,因为需要创建这个instance,之后的测试方法中才能使用到它,否则将调用真实的产品代码了)。我们通过“$clinit()”stub out了static block的初始化,这是必须的,因为在static block的初始化中,需要创建一个EntityManagerFactory。
在每个test case里面都会通过内部匿名类创建一个被mock掉的Email类,在@Mock中可以看到有一些属性,像invocations,maxInvocations等,就像是Expectations API中的times和maxTimes一个意思,虽然在示例中没有提到,但是constructors可以通过"$init"进行mock,并且参数必须是匹配的。
很显然,我们的大部分的工作可以使用Expectations API或者Verifications API来完成,但是毫无疑问,有时我们使用Mockups API也会很有乐趣(或者是某种情况下,使用Mockups API会更好使),因为这就好比做我们自己复写了所有的依赖,并且自己可以随意设定传入的参数以及对应的返回结果。这才像真正的剥离依赖~
好了,官方的示例代码就介绍到这了,下面将逐节的介绍他们的使用方式和使用特点,以应对我们各种需要。