EasyMock官方文档

来源:互联网 发布:淘宝老店新开 编辑:程序博客网 时间:2024/06/15 06:45


安装:

要求:

  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,还要添加Objenesis和Cglib到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>

Mocking

现在我们要创建一个test用例和玩具程序来理解EasyMock的功能。你也可以参考samples 和Getting 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对象到回放状态

下面是第一个例子:

@Beforepublic 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设置给ClassUnderTestlistenner属性。因为初始化的功能被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)定义调用次数。代码变成如下形式:

@Testpublic 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); }@Testpublic 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; }







1 0