2. Maven 依赖配置
在开始之前,先在 pom.xml
中添加以下依赖:
<dependency>
<groupId>org.easymock</groupId>
<artifactId>easymock</artifactId>
<version>3.5.1</version>
<scope>test</scope>
</dependency>
最新版本可在 Maven 中央仓库 查找。
3. 核心概念
使用 EasyMock 生成模拟对象时,我们可以模拟目标对象的行为,并最终验证其是否符合预期。操作分为四个步骤:
- 创建目标类的模拟对象
- 记录预期行为(包括动作、结果、异常等)
- 在测试中使用模拟对象
- 验证行为是否符合预期
记录完成后,需将模拟对象切换到"回放"模式,使其在协作时按记录行为执行。最后通过验证确保一切正常。
这些步骤对应 org.easymock.EasyMock
的核心方法:
- **
mock(...)
**:生成目标类(具体类或接口)的模拟对象。创建后处于"记录"模式,所有操作都会被记录 - **
expect(...)
**:设置预期行为(调用、结果、异常等) - **
replay(...)
**:切换到"回放"模式,触发方法调用时返回记录的结果 - **
verify(...)
**:验证所有预期是否满足,且没有未预期的调用
4. 实战模拟示例
假设有一个 Baeldung 博客读者,喜欢浏览文章并尝试写作。先创建模型类:
public class BaeldungReader {
private ArticleReader articleReader;
private IArticleWriter articleWriter;
// 构造方法
public BaeldungArticle readNext(){
return articleReader.next();
}
public List<BaeldungArticle> readTopic(String topic){
return articleReader.ofTopic(topic);
}
public String write(String title, String content){
return articleWriter.write(title, content);
}
}
包含两个私有成员:articleReader
(具体类)和 articleWriter
(接口)。接下来我们将模拟它们来验证 BaeldungReader
的行为。
5. Java 代码模拟
5.1 基础模拟
当读者跳过文章时,预期 articleReader.next()
会被调用:
@Test
public void whenReadNext_thenNextArticleRead(){
ArticleReader mockArticleReader = mock(ArticleReader.class);
BaeldungReader baeldungReader
= new BaeldungReader(mockArticleReader);
expect(mockArticleReader.next()).andReturn(null);
replay(mockArticleReader);
baeldungReader.readNext();
verify(mockArticleReader);
}
⚠️ 踩坑提示:即使不关心 mockArticleReader.next()
的返回值,也必须用 expect(...).andReturn(...)
指定返回值。对于有返回值的方法,直接调用会报错。
对于 void
方法,需用 expectLastCall()
:
mockArticleReader.someVoidMethod();
expectLastCall();
replay(mockArticleReader);
5.2 回放顺序控制
若需严格验证调用顺序:
@Test
public void whenReadNextAndSkimTopics_thenAllAllowed(){
ArticleReader mockArticleReader
= strictMock(ArticleReader.class);
BaeldungReade baeldungReader
= new BaeldungReader(mockArticleReader);
expect(mockArticleReader.next()).andReturn(null);
expect(mockArticleReader.ofTopic("easymock")).andReturn(null);
replay(mockArticleReader);
baeldungReader.readNext();
baeldungReader.readTopic("easymock");
verify(mockArticleReader);
}
使用 strictMock(...)
会严格检查方法调用顺序。而 niceMock(...)
则允许任意调用:
@Test
public void whenReadNextAndOthers_thenAllowed(){
ArticleReader mockArticleReader = niceMock(ArticleReader.class);
BaeldungReade baeldungReader = new BaeldungReader(mockArticleReader);
expect(mockArticleReader.next()).andReturn(null);
replay(mockArticleReader);
baeldungReader.readNext();
baeldungReader.readTopic("easymock"); // 未预期但允许
verify(mockArticleReader);
}
5.3 异常模拟
模拟接口 IArticleWriter
并验证异常抛出:
@Test
public void whenWriteMaliciousContent_thenArgumentIllegal() {
// 模拟和初始化代码
expect(mockArticleWriter
.write("easymock","<body onload=alert('baeldung')>"))
.andThrow(new IllegalArgumentException());
replay(mockArticleWriter);
// 执行写入并捕获异常
verify(mockArticleWriter);
assertEquals(
IllegalArgumentException.class,
expectedException.getClass());
}
通过 expect(...).andThrow(...)
记录预期行为:当检测到 XSS 攻击时抛出 IllegalArgumentException
。
6. 注解方式模拟
EasyMock 支持通过注解注入模拟对象。需使用 EasyMockRunner
运行测试:
@RunWith(EasyMockRunner.class)
public class BaeldungReaderAnnotatedTest {
@Mock
ArticleReader mockArticleReader;
@TestSubject
BaeldungReader baeldungReader = new BaeldungReader();
@Test
public void whenReadNext_thenNextArticleRead() {
expect(mockArticleReader.next()).andReturn(null);
replay(mockArticleReader);
baeldungReader.readNext();
verify(mockArticleReader);
}
}
✅ @Mock
注解的字段会被自动注入模拟对象,并注入到 @TestSubject
注解的类中。
若需使用其他测试运行器,可用 JUnit 规则 EasyMockRule
:
public class BaeldungReaderAnnotatedWithRuleTest {
@Rule
public EasyMockRule mockRule = new EasyMockRule(this);
// 其他代码...
@Test
public void whenReadNext_thenNextArticleRead(){
expect(mockArticleReader.next()).andReturn(null);
replay(mockArticleReader);
baeldungReader.readNext();
verify(mockArticleReader);
}
}
7. 使用 EasyMockSupport 批量管理
当测试中需要多个模拟对象时,手动重复 replay()
和 verify()
很繁琐:
replay(A);
replay(B);
replay(C);
//...
verify(A);
verify(B);
verify(C);
EasyMockSupport
类提供优雅解决方案:
public class BaeldungReaderMockSupportTest extends EasyMockSupport{
@Test
public void whenReadAndWriteSequencially_thenWorks(){
expect(mockArticleReader.next()).andReturn(null)
.times(2).andThrow(new NoSuchElementException());
expect(mockArticleWriter.write("title", "content"))
.andReturn("BAEL-201801");
replayAll(); // 批量回放
// 执行读写操作
verifyAll(); // 批量验证
assertEquals(
NoSuchElementException.class,
expectedException.getClass());
assertEquals("BAEL-201801", articleId);
}
}
关键点:
replayAll()
/verifyAll()
批量操作times(...)
指定方法调用次数,避免重复代码
也可通过委托方式使用:
EasyMockSupport easyMockSupport = new EasyMockSupport();
@Test
public void whenReadAndWriteSequencially_thenWorks(){
ArticleReader mockArticleReader = easyMockSupport
.createMock(ArticleReader.class);
IArticleWriter mockArticleWriter = easyMockSupport
.createMock(IArticleWriter.class);
BaeldungReader baeldungReader = new BaeldungReader(
mockArticleReader, mockArticleWriter);
expect(mockArticleReader.next()).andReturn(null);
expect(mockArticleWriter.write("title", "content"))
.andReturn("");
easyMockSupport.replayAll();
baeldungReader.readNext();
baeldungReader.write("title", "content");
easyMockSupport.verifyAll();
}
8. 总结
本文介绍了 EasyMock 的核心用法:
- 生成模拟对象
- 记录和回放行为
- 验证行为符合预期
✅ 优势:简单粗暴的 API,适合快速构建测试模拟
❌ 局限:相比 Mockito 等工具,社区活跃度稍低
对比 EasyMock、Mockito 和 JMockit 的详细分析,可参考这篇对比文章。完整示例代码见 GitHub 仓库。