介绍
EasyMock 的作用主要是方便在编写单元测试时,可以使用可以模拟出方法执行结果的对象,引导单元测试执行到所关心的代码,判断执行的结果。
引入依赖
maven 方式:
代码语言:javascript复制<dependency>
<groupId>org.easymock</groupId>
<artifactId>easymock</artifactId>
<version>4.2</version>
<scope>test</scope>
</dependency>
也可以下载独立的jar包 easymock-4.2.jar 放到classpath下,如果测试中需要对类进行mock,可以引入Objenesis 到classpath中。
EasyMock 3.2 版本开始增加了Android的支持,需要额外引入依赖:
代码语言:javascript复制<dependency>
<groupId>org.droidparts.dexmaker</groupId>
<artifactId>dexmaker</artifactId>
<version>1.5</version>
</dependency>
Mock 对象
可以使用mock方法来mock对象,引入静态方法:
代码语言:javascript复制import static org.easymock.EasyMock.*;
代码语言:javascript复制@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");
}
在 EasyMock 3.2 版本以上,还可以使用注解 @Mock 来注入,使用注解@TestSubject来指明mock对象注入到哪个测试对象里
代码语言:javascript复制@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");
}
}
在EasyMock3.3版本以上,支持Junit单元测试的@RunWith不是EasyMockRunner.class的情况下使用@Rule来启用EasyMock
代码语言:javascript复制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");
}
}
EasyMock4.1 以上,也对JUnit5 的Extension方式支持了。
代码语言:javascript复制@ExtendWith(EasyMockExtension.class)
public class ExampleTest {
@TestSubject
private ClassUnderTest classUnderTest = new ClassUnderTest();
@Mock
private Collaborator mock;
@Test
public void testRemoveNonExistingDocument() {
replay(mock);
classUnderTest.removeDocument("Does not exist");
}
}
有时需要有两个类型一样的Mock对象,注入到不同的对象中,可以在创建带有多个@Mock注解的对象,注解支持name属性来设置mock对象的名称,fieldName来设置注入到哪个属性中,type表示Mock对象的类型是NICE,还是STRICT(严格模式会校验方法调用的顺序)
代码语言:javascript复制@Mock(type = MockType.NICE, name = "mock", fieldName = "someField")
private Collaborator mock;
@Mock(type = MockType.STRICT, name = "anotherMock", fieldName = "someOtherField")
private Collaborator anotherMock;
EasyMockSupport 可以自动批量的注册、replay、重置或者验证,而不用逐个调用
可以让测试类继承来使用,也可以作为一个成员来使用,如果作为继承类来使用,可以直接调用父类的mock、replayAll、verifyAll方法,如果作为成员来使用,调用此成员变量的上述方法。
严格Mock 和 Nick Mock
严格模式创建的mock对象,默认方法为抛出异常,当调用到没有mock的方法,则会抛出异常。
Nice Mock 模式,默认方法是返回方法定义类型的默认值。
Mock 部分方法
部分方法被模拟,其他没有被模拟的方法将保持原来的行为
代码语言:javascript复制ToMock mock = partialMockBuilder(ToMock.class)
.addMockedMethod("mockedMethod").createMock();
Mock 构造函数需要参数的对象
默认创建mock对象使用的是无参构造器,但有的对象是需要给构造函数传递一些参数才能创建对象的,所以可以使用类似于下面这样的写法:
代码语言:javascript复制ToMock mock = partialMockBuilder(ToMock.class)
.withConstructor(1, 2, 3); // 1, 2, 3 are the constructor parameters
替代默认的构造器
默认使用的是 DefaultClassInstantiator,对于可序列化的类和其他能够猜出最佳构造函数和参数的情况下工作的不错。
不过如果要自己实现一个构造器的话,可以实现IClassInstantiator
接口,使用 ClassInstantiatorFactory.setInstantiator()
来设置自定义的构造器,如果要恢复使用默认的,可以调用setDefaultInstantiator()
注意: 构造器是一个静态的对象,所以在多个测试之间是公用的。确保在需要的时候reset状态。
类模拟的限制
- 为了保持一致,类模拟时
equals()
,toString()
,hashCode()
andfinalize()
方法具备一套内部的行为,不能被重写。 - final 的方法不能被mock,如果调用了final的方法,原始的代码会被执行
- private 的方法不能被mock,如果调用了,原始的代码会被助兴。
- 在部分mock情况下,如果你的测试调用了私有的方法,那么需要注意,这些私有的方法是没有被mock的
- 类的实例化用的是
Objenesis
,支持的JVM列表在这里 here
命名mock对象
mock的对象可以在创建的时候命名,`mock(String name, Class<T> toMock)
, strictMock(String name, Class<T> toMock)
或者 niceMock(String name, Class<T> toMock)
,名称将会在出现异常的时候看到。