EasyMock官方文档


安装:

要求:

  1. java1.5或以上
  2. 依赖包:Cglib (2.2+) 和 Objenesis (2.0+) 

使用MAVEN

Maven的中心仓库有EasyMock ,只需要添加如下的依赖到pom.xml:

<dependency>
  <groupId>org.easymock</groupId>
  <artifactId>easymock</artifactId>
  <version>3.3.1</version>
  <scope>test</scope>
</dependency>

独立部署

1.下载EasyMock zip文件

2.将easymock.jar添加到classpath

3.想使用mocking,还要添加ObjenesisCglib到classpath

4.下载的文件中还包含了javadoc、tests、sources和samples的jar包

安卓(3.2以后)

EasyMock可以在Android VM (Dalvik)上使用。仅需要将EasyMock和Dexmaker作为依赖添加到你要测试的apk项目中就可以了。Dexmaker代替了Cglib。如果你使用Maven,最后需要的依赖如下所示:

<dependency>
  <groupId>org.easymock</groupId>
  <artifactId>easymock</artifactId>
  <version>3.3.1</version>
  <exclusions>
    <exclusion>
      <groupId>cglib</groupId>
      <artifactId>cglib-nodep</artifactId>
    </exclusion>
  </exclusions>
</dependency>
<dependency>
  <groupId>com.google.dexmaker</groupId>
  <artifactId>dexmaker</artifactId>
  <version>1.1</version>
</dependency>

现在我们要创建一个test用例和玩具程序来理解EasyMock的功能。你也可以参考 samplesGetting Started.。

我们的第一个test是要检查删除一个不存在的文件是否不会通知给collaborator,下面是不使用Mock对象的test

import org.junit.*; 

public class ExampleTest {

  private ClassUnderTest classUnderTest; 
  
  private Collaborator mock; 
  
  @Before 
  public void setUp() { 
    classUnderTest = new ClassUnderTest(); 
    classUnderTest.setListener(mock); 
  } 
  
  @Test 
  public void testRemoveNonExistingDocument() { 
    // This call should not lead to any notification 
    // of the Mock Object: 
    classUnderTest.removeDocument("Does not exist"); 
  } 
}

在测试中经常使用到EasyMock的静态方法,我们可以使用静态导入org.easymock.EasyMock。

import static org.easymock.EasyMock.*;
import org.junit.*;

public class ExampleTest {
  private ClassUnderTest classUnderTest;
  private Collaborator mock;
}

要获取Mock对象,我们需要

  1. 为我们想要模拟的接口创建一个Mock对象
  2. 记录预期的行为
  3. 切换Mock对象到回放状态

下面是第一个例子:

@Before
public void setUp() {
  mock = mock(Collaborator.class); // 1
  classUnderTest = new ClassUnderTest(); 
  classUnderTest.setListener(mock); 
} 

@Test 
public void testRemoveNonExistingDocument() { 
  // 2 (we do not expect anything) 
  replay(mock); // 3 
  classUnderTest.removeDocument("Does not exist"); 
}

在步骤3激活以后,mock是一个Collaborator接口的Mock对象,不期望任何调用。这意味着如果我们更改ClassUnderTest去调用任何接口的方法,Mock对象都会抛出一个AssertionError异常:

java.lang.AssertionError: 
  Unexpected method call documentRemoved("Does not exist"): 
    at org.easymock.internal.MockInvocationHandler.invoke(MockInvocationHandler.java:29) 
    at org.easymock.internal.ObjectMethodsFilter.invoke(ObjectMethodsFilter.java:44) 
    at $Proxy0.documentRemoved(Unknown Source) 
    at org.easymock.samples.ClassUnderTest.notifyListenersDocumentRemoved(ClassUnderTest.java:74) 
    at org.easymock.samples.ClassUnderTest.removeDocument(ClassUnderTest.java:33) 
    at org.easymock.samples.ExampleTest.testRemoveNonExistingDocument(ExampleTest.java:24) 
      ...

使用注解(3.2版本以后)

使用注解可以以一种更友好、更短的方式去创建mock对象病将mock对象注入到被测试的class中。下面是上面的例子使用注解后的版本:

import static org.easymock.EasyMock.*; 
import org.easymock.EasyMockRunner;
import org.easymock.TestSubject; 
import org.easymock.Mock; 
import org.junit.Test; 
import org.junit.runner.RunWith;

@RunWith(EasyMockRunner.class) 
public class ExampleTest {

  @TestSubject 
  private ClassUnderTest classUnderTest = new ClassUnderTest(); // 2 
  
  @Mock 
  private Collaborator mock; // 1 
  
  @Test 
  public void testRemoveNonExistingDocument() { 
    replay(mock); 
    classUnderTest.removeDocument("Does not exist"); 
  } 
}

对象mock在步骤1被runner实例化。然后在步骤2被runner设置给 ClassUnderTest listenner属性。因为初始化的功能被runner承包了,所以setUp方法可以去掉了。

除此之外,从EasyMock3.3开始,如果你需要另一个ruuner,使用JUnit的rule也是可以的。选择哪一种方式是个人口味的问题。

import static org.easymock.EasyMock.*; 
import org.easymock.EasyMockRule; 
import org.easymock.TestSubject; 
import org.easymock.Mock; 
import org.junit.Rule; 
import org.junit.Test; 

public class ExampleTest {

  @Rule 
  public EasyMockRule mocks = new EasyMockRule(this); 
  
  @TestSubject 
  private ClassUnderTest classUnderTest = new ClassUnderTest(); 
  
  @Mock 
  private Collaborator mock; 
  
  @Test 
  public void testRemoveNonExistingDocument() {
    replay(mock); 
    classUnderTest.removeDocument("Does not exist"); 
  } 
}

注解有一个可选元素,“type”,用来描述mock是一个“nice” mock或者是"strict" mock. 另一个可选的注解,"name"可以为mock命名,在调用mock()方法的时候会用到,比如会出现在预期失败的提示信息里面。最后一个可选的元素视"fieldName",用来说明mock要注入的目标属性的名字。Mock实例可以注入到任何@TestSubject类型匹配的属性中。如果不止一个mock可以匹配到相同的目标类属性上就会被认为是error。这种场景下,用fieldName修饰可以避免这种模棱两可的匹配。

@Mock(type = MockType.NICE, name = "mock", fieldName = "someField") 
private Collaborator mock; 

@Mock(type = MockType.STRICT, name = "anotherMock", fieldName = "someOtherField") 
private Collaborator anotherMock;

EasyMockSupport

EasyMockSupport的存在是为了帮助你跟踪有的mock。你的测试用例可以继承它。它会自动的批量注册所有已经创建的mock实例并回放,重置或者是验证而无需明确的操作每一个mock。下面是例子:

public class SupportTest extends EasyMockSupport {

  private Collaborator firstCollaborator;
  private Collaborator secondCollaborator;
  private ClassTested classUnderTest;

  @Before
  public void setup() {
    classUnderTest = new ClassTested();
  }

  @Test
  public void addDocument() {
    // creation phase
    firstCollaborator = mock(Collaborator.class);
    secondCollaborator = mock(Collaborator.class);
    classUnderTest.addListener(firstCollaborator);
    classUnderTest.addListener(secondCollaborator);

    // recording phase
    firstCollaborator.documentAdded("New Document");
    secondCollaborator.documentAdded("New Document");
    replayAll(); // replay all mocks at once

    // test
    classUnderTest.addDocument("New Document", new byte[0]);
    verifyAll(); // verify all mocks at once
  }
}

Strict Mocks

通过 EasyMock.mock()方法反悔的Mock实例,不会检查方法调用的顺序。如果你想生成对方法调用顺序检查的strict Mock,用 EasyMock.strictMock()去创建。

如果在strict Mock对象上调用了一个不被预期的方法,异常的信息将会显示第一次引起冲突的方法调用的点上。verify(mock)会显示所有漏掉的方法调用。

Nice Mocks

对于使用mock()方法生成的Mock对象,所有非预期的方法调用都会抛出AssertionError异常。用niceMock()代替mock()生成的mock对象则默认可以调用所有方法,并返回一个合适的空值(0,null orfalse)。

Partial mocking

有时,你只想mock一个类的一部分方法,而另一部门保持原有类的行为。这种情况经常发生在你想测试一个方法,而这个方法要调用同一个类中的另外一个方法时。所以你想保留被测试方法的原有行为,而mock其他方法的行为。

在这种情况下,应该做的第一件事是思考重构,因为大部分情况下问题出在设计缺陷上。如果不是设计缺陷或者由于开发约束你不能这样做的时候,可以如下解决:

ToMock mock = partialMockBuilder(ToMock.class)
  .addMockedMethod("mockedMethod").createMock();

在这个例子中,只有通过addMockedMethod(s)添加的方法才会被mock(例子中的mockedMethod())。其他方法还会保持原有的行为。一种例外情况:抽象方法默认是被mock的。

partialMockBuilder  反回一个IMockBuilder接口。它包含了一些简单创建 partial mock 的方法。可以看一下javadoc。

备注EasyMock为Object的方法(equals, hashCode, toString, finalize)提供了默认实现。然而,对于 partial mock,如果这些方法没有被明确的mock,它们会保持原有的行为而不是EasyMock提供的默认行为。

自测试

你可以通过调用构造器来创建mock。当你想要测试一个类原本的方法,mock该类的其他方法时会很方便。如下所示:

ToMock mock = partialMockBuilder(ToMock.class)
  .withConstructor(1, 2, 3); // 1, 2, 3 are the constructor parameters

可以查看example中的ConstructorCalledMockTest 。

代替默认的类实例化方式

由于某种原因(通常是JVM不支持),EasyMock不能在你的环境中对class进行mock。类是通过工厂模式来实例化的。这种情况下的失败,你可以将默认的实例化替换成:

  • 一个很好的旧版本中的类DefaultClassInstantiator可以很好的实例化经过序列化的类,另外还会尽力选用最合适的构造器和参数。
  • 仅通过实现接口IClassInstantiator来获取实例。

你可以使用ClassInstantiatorFactory.setInstantiator()来设置新实例。你也可以使用setDefaultInstantiator()来设置回默认值。

重要:实例是静态的,所以在你的单元测试中是共享的。确保在必要的时候进行reset操作。

序列化类的mock

类的mock也可以被序列化。然而,因为它继承了一个可序列化的类,这个类可能已经通过比如writeObject方法定义了特殊的行为。这些方法在序列化mock的时候仍然会被调用并可能引起失败。这种情况,可通过调用构造器来创建mock进行变通处理。

尽管如此,在另外一个class loader反序列化mock实例然后再序列化可能会失败,这种情况没有经过测试。


mocking类的限制


  • 对接口进行mock,EasyMock提供了一个内置的行为,比如equals()toString()hashCode() 和 finalize(),对类的mock也一样。这也意味着你无法记录你自己提供的这些方法的行为。这个限制是EasyMock的一个失误,因为它妨碍你去关心这些方法。
  • final方法不能被mock。如果进行调用,他们原有的逻辑会被执行
  • 私有方法不能被mock。如果对私有方法进行调用,那么原本的行为会被执行。在partial mocking中,如果你测试的方法调用了其他的私有方法,你需要进行额外的测试,因为你不能mock那些被调用的私有方法。
  • 类的实例化使用Objenesis控制。查看支持的JVM列表here.

给Mock对象命名

可以使用mock(String name, Class<T> toMock),strictMock(String name, Class<T> toMock) 或者 niceMock(String name, Class<T> toMock)方法创建Mock对象,并给Mock对象命名。Mock的名字会显示在异常信息中。

第二个测试

我们来写第二个测试。如果在被测试的类中添加文件,我们预期会调用mock对象的mock.documentAdded()方法,参数是文档的标题。

@Test 
public void testAddDocument() {
  mock.documentAdded("New Document"); // 2
  replay(mock); // 3
  classUnderTest.addDocument("New Document", new byte[0]);
}

在记录的状态中(调用 replay之前),Mock对象并没有Mock对象的行为,但是它记录了方法的调用。在调用replay之后,它就有了Mock对象的行为,会检查预期的方法是否真的被调用。

如果classUnderTest.addDocument("New Document", new byte[0])调用了预期的方法,但是使用了错误的参数,Mock对象会抛出AssertionError

java.lang.AssertionError: 
  Unexpected method call documentAdded("Wrong title"): 
    documentAdded("New Document"): expected: 1, actual: 0 
      at org.easymock.internal.MockInvocationHandler.invoke(MockInvocationHandler.java:29) 
      at org.easymock.internal.ObjectMethodsFilter.invoke(ObjectMethodsFilter.java:44) 
      at $Proxy0.documentAdded(Unknown Source) 
      at org.easymock.samples.ClassUnderTest.notifyListenersDocumentAdded(ClassUnderTest.java:61) 
      at org.easymock.samples.ClassUnderTest.addDocument(ClassUnderTest.java:28) 
      at org.easymock.samples.ExampleTest.testAddDocument(ExampleTest.java:30) 
      ...

所有遗漏的预期调用都会被显示,不被预期的方法调用也会被显示。如果方法调用执行的次数过多,Mock对象也会抛出异常:

java.lang.AssertionError: 
  Unexpected method call documentAdded("New Document"): 
    documentAdded("New Document"): expected: 1, actual: 2 
      at org.easymock.internal.MockInvocationHandler.invoke(MockInvocationHandler.java:29) 
      at org.easymock.internal.ObjectMethodsFilter.invoke(ObjectMethodsFilter.java:44) 
      at $Proxy0.documentAdded(Unknown Source) 
      at org.easymock.samples.ClassUnderTest.notifyListenersDocumentAdded(ClassUnderTest.java:62) 
      at org.easymock.samples.ClassUnderTest.addDocument(ClassUnderTest.java:29) 
      at org.easymock.samples.ExampleTest.testAddDocument(ExampleTest.java:30) 
      ...

改变相同方法调用的行为

方法的行为是可以被改变的。方法的调用次数,返回值,抛出的异常可以链式调用。举个例子,我们对voteForRemoval("Document")方法定义如下:

  • 前三次调用返回42
  • 接下来的四次调用抛出RuntimeException
  • 接下来返回一次-42

expect(mock.voteForRemoval("Document"))
  .andReturn((byte) 42).times(3) 
  .andThrow(new RuntimeException(), 4) 
  .andReturn((byte) -42);

改变EasyMock默认的行为

EasyMock支持通过配置property来改变它的行为。 主要的目的是允许在新版本中使用旧版本遗留的行为。目前支持的属性包括:

easymock.notThreadSafeByDefault

            如果为true,mock不是线程安全的。默认为false。
easymock.enableThreadSafetyCheckByDefault             如果为true,默认会打开线程安全检查特性。默认值为false。
easymock.disableClassMocking             不允许对类进行mocking(只允许接口的mocking)。默认是false。

以上属性可以通过两种方式设置:

  • classpath默认包的easymock.properties文件
  • 调用EasyMock.setEasyMockProperty方法。EasyMock类中的常量是可用的。在编码中设置属性会覆盖easymock.properties文件中设置的属性。

Object方法

对于EasyMock创建的Mock对象,Object的四个方法equals(), hashCode(),toString() andfinalize()的行为不能被改变,即使他们是Mock对象所实现的接口中的一部分。

为方法使用Stub行为

有时,我们希望我们的Mock对象响应一些方法调用,但是我们不想检查该方法被调用几次,甚至是有没有被调用。stub行为可以使用方法sandStubReturn(Object value),andStubThrow(Throwable throwable),andStubAnswer(IAnswer<T> answer)asStub()来定义。下面的代码把Mock对象构造成调用voteForRemoval("Document")返回一次42,其它参数返回-1.

expect(mock.voteForRemoval("Document")).andReturn(42); 
expect(mock.voteForRemoval(not(eq("Document")))).andStubReturn(-1);

重用Mock对象

Mock Objects may be reset by reset(mock).

If needed, a mock can also be converted from one type to another by calling resetToNice(mock), resetToDefault(mock) ou resetToStrict(mock).

Mock对象可以通过reset(mock)重置。

如果需要的话,mock可以通过调用方法resetToNice(mock), resetToDefault(mock) ouresetToStrict(mock).从一种类型转换为另一种类型。

第一个验证

到目前,我们还有一个error没有处理。如果我们定义了行为,我们希望验证它真的起作用了。目前的test如果Mock的方法没有被调用是可以通过的。为了验证定义的行为被使用了,我们必须要调用verify(mock)

@Test 
public void testAddDocument() {
  mock.documentAdded("New Document"); // 2
  replay(mock); // 3
  classUnderTest.addDocument("New Document", new byte[0]);
  verify(mock);
}

现在,如果Mock对象的方法没有被调用,我们能够得到下面的异常:

java.lang.AssertionError: 
  Expectation failure on verify: 
    documentAdded("New Document"): expected: 1, actual: 0 
      at org.easymock.internal.MocksControl.verify(MocksControl.java:70) 
      at org.easymock.EasyMock.verify(EasyMock.java:536) 
      at org.easymock.samples.ExampleTest.testAddDocument(ExampleTest.java:31) 
      ...

异常的信息列出了所有遗漏的预期调用。

预期指定次数的调用

到目前为止,我们的test只考虑了一个方法的调用。下面的test会检查添加一个已经存在的文件会通过合适的参数调用mock.documentChanged()方法。为了确保效果,我们对此检查3次。

@Test 
public void testAddAndChangeDocument() {
  mock.documentAdded("Document");
  mock.documentChanged("Document");
  mock.documentChanged("Document");
  mock.documentChanged("Document");
  replay(mock);
  classUnderTest.addDocument("Document", new byte[0]);
  classUnderTest.addDocument("Document", new byte[0]);
  classUnderTest.addDocument("Document", new byte[0]);
  classUnderTest.addDocument("Document", new byte[0]);
  verify(mock);
}

为了避免重复写mock.documentChanged("Document"),EasyMock提供了一个渐变的方法,我们可以通过调用通过expectLastCall()方法返回的对象的times(int times)定义调用次数。代码变成如下形式:

@Test
public void testAddAndChangeDocument() {
  mock.documentAdded("Document"); 
  mock.documentChanged("Document"); 
  expectLastCall().times(3); 
  replay(mock); 
  classUnderTest.addDocument("Document", new byte[0]); 
  classUnderTest.addDocument("Document", new byte[0]); 
  classUnderTest.addDocument("Document", new byte[0]); 
  classUnderTest.addDocument("Document", new byte[0]); 
  verify(mock);
}

如果方法调用超出预期的次数,我们会得到异常,噶偶我们方法被调用的次数太多了。异常在第一次超出预期次数的方法调用时立即触发。

java.lang.AssertionError:
  Unexpected method call documentChanged("Document"): 
    documentChanged("Document"): expected: 3, actual: 4 
      at org.easymock.internal.MockInvocationHandler.invoke(MockInvocationHandler.java:29) 
      at org.easymock.internal.ObjectMethodsFilter.invoke(ObjectMethodsFilter.java:44) 
      at $Proxy0.documentChanged(Unknown Source) 
      at org.easymock.samples.ClassUnderTest.notifyListenersDocumentChanged(ClassUnderTest.java:67) 
      at org.easymock.samples.ClassUnderTest.addDocument(ClassUnderTest.java:26) 
      at org.easymock.samples.ExampleTest.testAddAndChangeDocument(ExampleTest.java:43) 
      ...

如果调用的次数太少,verify(mock)抛出如下异常。

java.lang.AssertionError: 
  Expectation failure on verify: 
    documentChanged("Document"): expected: 3, actual: 2 
      at org.easymock.internal.MocksControl.verify(MocksControl.java:70) 
      at org.easymock.EasyMock.verify(EasyMock.java:536) 
      at org.easymock.samples.ExampleTest.testAddAndChangeDocument(ExampleTest.java:43)
      ...

定义返回值

定义返回值,我们用expect(T value)来包装预期的方法调用,用andReturn(Object returnValue)来定义返回值,andReturn(Object returnValue)是通过调用expect(T value)返回的对象的方法

举例说明,我们检查删除文档的工作流。如果ClassUnderTest的删除文件函数被调用,它会询问所有的collaborators,collaborators会通过调用byte voteForRemoval(String title)来对删除文件操作进行投票。正直则删除文件,文件会被删除,并且所有的collaborators都会调用documentRemoved(String title)方法。

@Test 
public void testVoteForRemoval() {
  mock.documentAdded("Document");
  // expect document addition 
  // expect to be asked to vote for document removal, and vote for it 
  expect(mock.voteForRemoval("Document")).andReturn((byte) 42);
  mock.documentRemoved("Document");
  // expect document removal 
  replay(mock); 
  classUnderTest.addDocument("Document", new byte[0]); 
  assertTrue(classUnderTest.removeDocument("Document")); 
  verify(mock); 
}

@Test
public void testVoteAgainstRemoval() {
  mock.documentAdded("Document");
  // expect document addition 
  // expect to be asked to vote for document removal, and vote against it 
  expect(mock.voteForRemoval("Document")).andReturn((byte) -42);
  replay(mock);
  classUnderTest.addDocument("Document", new byte[0]);
  assertFalse(classUnderTest.removeDocument("Document"));
  verify(mock);
}

返回只类型在编译的时候检查。举例说明,下面的代码不会通过编译,因为返回值提供的类型和方法的返回值不匹配。

expect(mock.voteForRemoval("Document")).andReturn("wrong type");


取代调用expect(T value)来获取对象并设置返回值,我们可以调用expectLastCall()来返回对象。换成如下形式:

expect(mock.voteForRemoval("Document")).andReturn((byte) 42);


我们还可以使用:

mock.voteForRemoval("Document"); 
expectLastCall().andReturn((byte) 42);


这种定义方式只应该在代码太长的时候使用,因为它不支持编译器的类型检查。

处理异常

定义抛出的exceptions(更准确的说是Throwables),通过expectLastCall()或者expect(Tvalue)返回的对象提供了方法andThrow(Throwable throwable)

未检查异常(也就是RuntimeExceptionError和所有他们的子类)可能从任何一个方法抛出。检查型异常只能从真正抛出他们的方法中抛出。


创建返回值或异常

有时我们希望我们的mock对象在实际调用的时候返回一个值或者是抛出一个异常。从EasyMock2.2版本开始,通过expectLastCall()expect(T value)返回的mock对象提供了

andAnswer(IAnswer answer)方法,该方法允许定义IAnswer的实现类用来创造返回值或者是异常。

IAnswer回调方法的内部,通过EasyMock.getCurrentArguments()可以得到调用mock方法的参数。如果你这样使用,像重新定义参数或者改变参数顺序的重构都会破坏你的用例,以一定要小心使用

另一个替代IAnswer的选择是andDelegateToandStubDelegateTo方法。他们允许将方法调用委托给一个被mock的接口的实现,并由这个实现提供答案。赞成的观点是,在IAnswer中通过EasyMock.getCurrentArguments()获取调用参数现在变成了通过实现类的方法调用。这是重构安全的。反对者的观点是你不得不手动的提供一个mock实现...这正是你想用EasyMock用力避免的事情。如果接口有很多方法的话也比较让人苦恼。最后,mock的类型和实现类的类型不能得到静态的检查。如果出于某种原因,实现类没有实现被委托的方法。你会得到异常。然而,这种情况是非常少的。

为了正确理解这两种选择,这里有一个例子:

List<String> l = mock(List.class);

// andAnswer style 
expect(l.remove(10)).andAnswer(new IAnswer<String>() {
  public String answer() throws Throwable {
    return getCurrentArguments()[0].toString();
  } 
}); 

// andDelegateTo style 
expect(l.remove(10)).andDelegateTo(new ArrayList<String>() {
  @Override 
  public String remove(int index) {
    return Integer.toString(index); 
  } 
});


检查多个Mock对象方法的调用顺序

到目前为止,我们看到的都是通过EasyMock的静态方法构造的一个单独的mock对象。但是,大部分这些静态方法只是隐式的控制mock对象并对该对象进行委托。Mock对象的控制是IMocksControl接口的实现。

所以,下面的代码

IMyInterface mock = strictMock(IMyInterface.class);
replay(mock);
verify(mock);
reset(mock);


可以替换为

IMocksControl ctrl = createStrictControl();
IMyInterface mock = ctrl.createMock(IMyInterface.class);
ctrl.replay();
ctrl.verify();
ctrl.reset();


IMocksControl允许创建多个Mock对象,所以它可以检查多个mock对象的方法调用顺序。举例说明,我们创建两个IMyInterface的mock,然后我们预期按照mock1.a()mock2.a()的顺序进行调用,然后任意次数的调用mock1.c()和mock2.c(),最后按照mock2.b()和 mock1.b()的顺序进行调用。

IMocksControl ctrl = createStrictControl();
IMyInterface mock1 = ctrl.createMock(IMyInterface.class);
IMyInterface mock2 = ctrl.createMock(IMyInterface.class);
mock1.a();
mock2.a();
ctrl.checkOrder(false);
mock1.c();
expectLastCall().anyTimes();
mock2.c();
expectLastCall().anyTimes();
ctrl.checkOrder(true);
mock2.b();
mock1.b();
ctrl.replay();


放松调用次数

放松预期方法的调用次数,还有几个额外的方法用来代替times(int count)

times(int min, int max)
预期调用min到max次
atLeastOnce()

最少一次

anyTimes()
任意次

如果没有定义预期次数,默认为一次。如果准确的表述可以用once() 或者 times(1)

控制方法调用顺序检查的开关

有的时候,我们只想检查Mock对象一部分方法调用的顺序。在记录阶段,你可以调用checkOrder(mock, true)打开顺序检查,调用checkOrder(mock, false)关闭顺序检查

strict Mock 对象和normal Mock 对象:有两个区别:

  1. strict Mock对象在创建之后有顺序检查
  2. strict Mock对象在调用reset后有顺序检查

灵活的参数匹配预期

为了匹配Mock对象的一个预期的方法的调用,对象的参数默认通过equals()来比较。但是这样会导致问题。比如,我们考虑下面的预期:

String[] documents = new String[] { "Document 1", "Document 2" };
expect(mock.voteForRemovals(documents)).andReturn(42);

如果方法调用使用了另外一个包含同样内容的数组,我们会得到一个exception,因为equals()对于数组比较的是对象引用是否相同。

java.lang.AssertionError: 
  Unexpected method call voteForRemovals([Ljava.lang.String;@9a029e):
    voteForRemovals([Ljava.lang.String;@2db19d): expected: 1, actual: 0 
    documentRemoved("Document 1"): expected: 1, actual: 0 
    documentRemoved("Document 2"): expected: 1, actual: 0 
      at org.easymock.internal.MockInvocationHandler.invoke(MockInvocationHandler.java:29) 
      at org.easymock.internal.ObjectMethodsFilter.invoke(ObjectMethodsFilter.java:44) 
      at $Proxy0.voteForRemovals(Unknown Source) 
      at org.easymock.samples.ClassUnderTest.listenersAllowRemovals(ClassUnderTest.java:88) 
      at org.easymock.samples.ClassUnderTest.removeDocuments(ClassUnderTest.java:48) 
      at org.easymock.samples.ExampleTest.testVoteForRemovals(ExampleTest.java:83) 
      ...


为了指定方法调用使用相同的数组,我们需要使用EasyMock的静态方法aryEq来进行检查

String[] documents = new String[] { "Document 1", "Document 2" };
expect(mock.voteForRemovals(aryEq(documents))).andReturn(42);


如果你想在方法调用中使用参数匹配,你需要指定每个参数的匹配规则。

下面只一组预定义的匹配方法:

eq(X value)

判断实际的值和预期的值是否相同。对于基础数据类型有效。

anyBoolean(), anyByte(), anyChar(), anyDouble(), anyFloat(), anyInt(), anyLong(), anyObject(), anyObject(Class clazz), anyShort(), anyString() 匹配任何值,对于所有的基础数据类型有效。

eq(X value, X delta)

匹配实际的值和给出的值是否想能,对于float和double有效

aryEq(X value) 通过调用 Arrays.equals()判断实际值和给出的值是否相同。对于基础数据类型数组有效。
isNull(), isNull(Class clazz) 判断实际值是否为空。
notNull(), notNull(Class clazz) 判断实际值是否非空
same(X value) 判断实际值是否和给定的值为同一个对象
isA(Class clazz) 判断实际值是否是一个接口或类,或者是否为一个给定类的子接口或子类。Null总是返回false。
lt(X value), leq(X value), geq(X value), gt(X value) 判断实际值是否小于、小于等于、大于等于、大于给定的值。对于基础数据类型和实现了 Comparable的对象有效
startsWith(String prefix), contains(String substring), endsWith(String suffix) 判断实际值是否开始、包含、结束给定值,对于String类型有效。
matches(String regex), find(String regex) 判断实际值或子字符串是否匹配给定的正则表达式。对于String类型有效。
and(X first, X second) 判断实际值是否同时满足first和second条件。对于基础数据类型有效。
or(X first, X second) 判断实际值是否满足first或second条件。对于基础数据类型有效。
not(X value) 判断实际值是否和给定值不匹配
cmpEq(X value) 判断实际值和给定值是否相同,通过 Comparable.compareTo(X o)进行比较。对于数字类型的基础数据类型和实现了 Comparable接口的类有效
cmp(X value, Comparator<X> comparator, LogicalOperator operator) 判断实际值和匹配值是否满足 comparator.compare(actual, value)方法的比较结果, operator可以是<,<=,>,>= or ==。对于对象有效

定义自己的参数匹配规则

有时,我们希望定义自己的参数匹配规则。比如说你要定义一个exception的参数匹配规则是参数类型和message都相同。应该使用下面的方法:

IllegalStateException e = new IllegalStateException("Operation not allowed.")
expect(mock.logThrowable(eqException(e))).andReturn(true);

通过两个步骤可以达到:定义新的参数匹配规则,声明静态方法eqException

定义新的参数匹配规则,我们通过实现接口org.easymock.IArgumentMatcher来实现。这个接口包含了两个方法:matches(Object actual)用来检查实际的参数和给定的参数是否匹配,另一个方法appendTo(StringBuffer buffer) 将参数匹配的描述追加到给定的string buffer上。实现如下:

import org.easymock.IArgumentMatcher; 

public class ThrowableEquals implements IArgumentMatcher { 
  
  private Throwable expected; 
  
  public ThrowableEquals(Throwable expected) {
    this.expected = expected;
  }
  
  public boolean matches(Object actual) {
    if (!(actual instanceof Throwable)) {
      return false;
    }
    
    String actualMessage = ((Throwable) actual).getMessage();
    return expected.getClass().equals(actual.getClass()) && expected.getMessage().equals(actualMessage);
  }
  
  public void appendTo(StringBuffer buffer) {
    buffer.append("eqException("); 
    buffer.append(expected.getClass().getName()); 
    buffer.append(" with message \""); 
    buffer.append(expected.getMessage()); 
    buffer.append("\"")"); 
  } 
}


eqException方法比用用给定的Throwable创建参数匹配规则,通过EasyMock的静态方法reportMatcher(IArgumentMatcher matcher)报告给EasyMock并且返回一个可能用在方法调用内部的值(典型的如:0,null orfalse).第一个尝试看起来如下:

public static Throwable eqException(Throwable in) {
  EasyMock.reportMatcher(new ThrowableEquals(in)); 
  return null;
}

然而,只有在例子中的logThrowable方法接收Throwable异常,而不是更一般的如RuntimeException异常的时候才会生效。在以后的版本,我们的例子将不会通过编译

IllegalStateException e = new IllegalStateException("Operation not allowed.")
expect(mock.logThrowable(eqException(e))).andReturn(true);


Java 5.0 比较保险的方法是:不用Throwable来定义eqException的参数和返回值,我们使用一个继承Throwable的类。

public static<T extends Throwable>T eqException(T in) { 
  reportMatcher(new ThrowableEquals(in)); 
  return null; 
}







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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值