1. 概述

本文将深入探讨 EasyMock 参数匹配器的使用技巧。我们将系统介绍各类预定义匹配器,并演示如何创建自定义匹配器

如果你对 EasyMock 基础还不熟悉,建议先阅读我们的 EasyMock 入门指南。本文假设你已经掌握了 EasyMock 的基本用法。

2. 基础 Mock 示例

在探索各种匹配器之前,先明确我们的测试场景。本文所有示例都基于一个简单的用户服务。

这是我们的核心接口 IUserService

public interface IUserService {
    public boolean addUser(User user);
    public List<User> findByEmail(String email);
    public List<User> findByAge(double age);  
}

对应的 User 实体类:

public class User {
    private long id;
    private String firstName;
    private String lastName;
    private double age;
    private String email;

    // 标准构造器、getter/setter 方法
}

首先创建基础 mock 对象供后续使用:

private IUserService userService = mock(IUserService.class);

现在开始探索 EasyMock 的参数匹配器世界。

3. 等值匹配器

使用 eq() 匹配器验证新增用户:

@Test
public void givenUserService_whenAddNewUser_thenOK() {        
    expect(userService.addUser(eq(new User()))).andReturn(true);
    replay(userService);

    boolean result = userService.addUser(new User());
    verify(userService);
    assertTrue(result);
}

✅ 该匹配器支持基本类型和对象,对象比较使用 equals() 方法

使用 same() 匹配器验证特定用户实例:

@Test
public void givenUserService_whenAddSpecificUser_thenOK() {
    User user = new User();
    
    expect(userService.addUser(same(user))).andReturn(true);
    replay(userService);

    boolean result = userService.addUser(user);
    verify(userService);
    assertTrue(result);
}

⚠️ same() 使用 == 比较对象引用,而非值比较。

默认情况下(不使用匹配器时),EasyMock 使用 equals() 进行参数比较。对于数组,可使用基于 Arrays.equals()aryEq() 匹配器。

4. 任意类型匹配器

提供多种 any 系列匹配器:anyInt()anyBoolean()anyDouble() 等。这些匹配器要求参数为指定类型

示例:使用 anyString() 匹配任意邮箱字符串:

@Test
public void givenUserService_whenSearchForUserByEmail_thenFound() {
    expect(userService.findByEmail(anyString()))
      .andReturn(Collections.emptyList());
    replay(userService);

    List<User> result = userService.findByEmail("test@example.com");
    verify(userService);
    assertEquals(0,result.size());
}

使用 isA() 验证参数类型:

@Test
public void givenUserService_whenAddUser_thenOK() {
    expect(userService.addUser(isA(User.class))).andReturn(true);
    replay(userService);

    boolean result = userService.addUser(new User());
    verify(userService);
    assertTrue(result);
}

这里我们断言 addUser() 方法的参数必须是 User 类型。

5. 空值匹配器

使用 isNull()notNull() 匹配 null 值:

@Test
public void givenUserService_whenAddNull_thenFail() {
    expect(userService.addUser(isNull())).andReturn(false);
    replay(userService);

    boolean result = userService.addUser(null);
    verify(userService);
    assertFalse(result);
}

类似地,使用 notNull() 验证非空值:

@Test
public void givenUserService_whenAddNotNull_thenOK() {
    expect(userService.addUser(notNull())).andReturn(true);
    replay(userService);

    boolean result = userService.addUser(new User());
    verify(userService);
    assertTrue(result);
}

6. 字符串匹配器

提供多个专用字符串匹配器:

使用 startsWith() 匹配邮箱前缀:

@Test
public void whenSearchForUserByEmailStartsWith_thenFound() {        
    expect(userService.findByEmail(startsWith("test")))
      .andReturn(Collections.emptyList());
    replay(userService);

    List<User> result = userService.findByEmail("test@example.com");
    verify(userService);
    assertEquals(0,result.size());
}

使用 endsWith() 匹配邮箱后缀:

@Test
public void givenUserService_whenSearchForUserByEmailEndsWith_thenFound() {        
    expect(userService.findByEmail(endsWith(".com")))
      .andReturn(Collections.emptyList());
    replay(userService);

    List<User> result = userService.findByEmail("test@example.com");
    verify(userService);
    assertEquals(0,result.size());
}

使用 contains() 匹配包含子串的邮箱:

@Test
public void givenUserService_whenSearchForUserByEmailContains_thenFound() {        
    expect(userService.findByEmail(contains("@")))
      .andReturn(Collections.emptyList());
    replay(userService);

    List<User> result = userService.findByEmail("test@example.com");
    verify(userService);
    assertEquals(0,result.size());
}

使用 matches() 进行正则匹配:

@Test
public void givenUserService_whenSearchForUserByEmailMatches_thenFound() {        
    expect(userService.findByEmail(matches(".+\\@.+\\..+")))
      .andReturn(Collections.emptyList());
    replay(userService);

    List<User> result = userService.findByEmail("test@example.com");
    verify(userService);
    assertEquals(0,result.size());
}

7. 数值匹配器

提供多个数值专用匹配器:

使用 lt() 匹配小于 100 的年龄:

@Test
public void givenUserService_whenSearchForUserByAgeLessThan_thenFound() {    
    expect(userService.findByAge(lt(100.0)))
      .andReturn(Collections.emptyList());
    replay(userService);

    List<User> result = userService.findByAge(20);        
    verify(userService);
    assertEquals(0,result.size());
}

使用 geq() 匹配大于等于 10 的年龄:

@Test
public void givenUserService_whenSearchForUserByAgeGreaterThan_thenFound() {    
    expect(userService.findByAge(geq(10.0)))
      .andReturn(Collections.emptyList());
    replay(userService);

    List<User> result = userService.findByAge(20);        
    verify(userService);
    assertEquals(0,result.size());
}

可用数值匹配器清单:

  • lt() – 小于指定值
  • leq() – 小于等于
  • gt() – 大于
  • geq() – 大于等于

8. 组合匹配器

使用 and()or()not() 组合多个匹配器

组合 gt()lt() 验证年龄范围:

@Test
public void givenUserService_whenSearchForUserByAgeRange_thenFound() {
    expect(userService.findByAge(and(gt(10.0),lt(100.0))))
      .andReturn(Collections.emptyList());
    replay(userService);

    List<User> result = userService.findByAge(20);        
    verify(userService);
    assertEquals(0,result.size());
}

组合 not()endsWith() 排除 .com 后缀邮箱:

@Test
public void givenUserService_whenSearchForUserByEmailNotEndsWith_thenFound() {
    expect(userService.findByEmail(not(endsWith(".com"))))
      .andReturn(Collections.emptyList());
    replay(userService);

    List<User> result = userService.findByEmail("test@example.org");
    verify(userService);
    assertEquals(0,result.size());
}

9. 自定义匹配器

最后演示如何创建自定义匹配器。目标实现 minCharCount() 匹配器,验证字符串长度:

@Test
public void givenUserService_whenSearchForUserByEmailCharCount_thenFound() {        
    expect(userService.findByEmail(minCharCount(5)))
      .andReturn(Collections.emptyList());
    replay(userService);

    List<User> result = userService.findByEmail("test@example.com");
    verify(userService);
    assertEquals(0,result.size());
}

创建自定义匹配器需要两个步骤:

  1. 实现 IArgumentMatcher 接口
  2. 创建静态方法并通过 reportMatcher() 注册匹配器实例

实现代码如下(使用匿名内部类):

public static String minCharCount(int value){
    EasyMock.reportMatcher(new IArgumentMatcher() {
        @Override
        public boolean matches(Object argument) {
            return argument instanceof String 
              && ((String) argument).length() >= value;
        }
 
        @Override
        public void appendTo(StringBuffer buffer) {
            buffer.append("charCount(\"" + value + "\")");
        }
    });    
    return null;
}

❌ 注意:IArgumentMatcher 接口包含两个方法:

  • matches():包含参数验证逻辑
  • appendTo():定义匹配器在错误信息中的字符串表示

10. 总结

我们系统学习了 EasyMock 的预定义参数匹配器,并掌握了创建自定义匹配器的方法。合理使用匹配器能让单元测试更简洁、更健壮。

完整示例代码请查看 GitHub 项目


原始标题:EasyMock Argument Matchers