Mockito 的 anyString(), any(Foo.class) 等不能匹配 null 值

使用 Mockito Mock 方法式,一直以为可以用 anyString(), any(Foo.class) 等匹配 null 值,其实不行,null 值必须显式的用 null, 或 eq(null) 来匹配。anyString(), anyInt() 等只能匹配非 null 值,查看它们的返回值实际是 "" 和 0 等, 而更为特别的是 any(Foo.class) 看到的是 null, 仍然不能匹配 null 值。进一步用 Mockito.mockingDetails(mock).printInvocations() 打印出的内容,anyString(), any(Foo.class) 都会显示为 null 值。


说的有点罗嗦,看下面的例子, 被测试类 UserDao,sql 和 sqlArguments 由各自的 setter 方法来控制,默认它们都为 null
 1public class UserDao {
 2
 3    private final NamedParameterJdbcTemplate jdbcTemplate;
 4
 5    private String sql;
 6    private MapSqlParameterSource sqlArguments;
 7
 8    public void setSql(String sql) {
 9        this.sql = sql;
10    }
11
12    public void setSqlArguments(MapSqlParameterSource sqlArguments) {
13       this.sqlArguments = sqlArguments;
14    }
15
16    public UserDao(NamedParameterJdbcTemplate jdbcTemplate) {
17        this.jdbcTemplate = jdbcTemplate;
18    }
19
20    public List<String> fetchUsers() {
21        return jdbcTemplate.query(sql, sqlArguments, (rs, idx) -> rs.getString("name"));
22    }
23}

来它的测试类 UserDaoTest
 1@RunWith(MockitoJUnitRunner.class)
 2public class UserDaoTest {
 3
 4    @Mock
 5    private NamedParameterJdbcTemplate namedParameterJdbcTemplate;
 6
 7    @InjectMocks
 8    private UserDao userDao;
 9
10
11    @Test
12    public void testFetchUsers() {
13        when(namedParameterJdbcTemplate.query(anyString(),
14            any(MapSqlParameterSource.class),
15            ArgumentMatchers.<RowMapper<String>>any()))
16            .thenReturn(Arrays.asList("Hello", "Ketty"));
17
18        List<String> users = userDao.fetchUsers();
19        assertThat(users).hasSize(2);
20    }
21}

测试不成功,输出信息为
java.lang.AssertionError:
Expected size:<2> but was:<0> in:
<[]>
空为 Mock 对象 namedParameterJdbcTemplate.query(...) 方法返回类型的默认值

原以为 anyString()any(MapSqlParameterSource.class) 可以匹配到实际中的 sql = nullsqlArguments = null 值,都说是 any 却不能对应到 null 值。如果在 assertThat(users).hasSize(2) 之前加上
1System.out.println(Mockito.mockingDetails(namedParameterJdbcTemplate).getInvocations());

再执行看到的输出为
[namedParameterJdbcTemplate.query(
    null,
    null,
    yanbin.blog.UserDao$$Lambda$1/1192672907@363a52f
);]
mockingDetails 告诉我们前两个参数的实际值为 null, 不能用 anyString()any(MapSqlParameterSource.class) 来匹配.

查看 ArgumentMatchers.anyString() 的值
1public static String anyString() {
2    reportMatcher(new InstanceOf(String.class, "<any string>"));
3    return "";
4}

似乎 anyString() 的值为 "", "" 不能匹配 null 值。断点调试中查看 anyString()  和 any(MapSqlParameterSourcec.class) 的值

注意,查看它们的值时会有异常,即 reportMatcher 报告出来的。

看起来 ArgumentMatchers.any(MapSqlParameterSource.class) 像是个真的 null 值,所以我们下面只把 anyString() 换成 eq(null) 来看下

 1    @Test
 2    public void testFetchUsers() {
 3        when(namedParameterJdbcTemplate.query(eq(null),
 4            any(MapSqlParameterSource.class),
 5            ArgumentMatchers.<RowMapper<String>>any()))
 6            .thenReturn(Arrays.asList("Hello", "Ketty"));
 7
 8        List<String> users = userDao.fetchUsers();
 9        assertThat(users).hasSize(2);
10    }

还是不成功,同样的出错信息
java.lang.AssertionError:
Expected size:<2> but was:<0> in:
<[]>
实际运行中的两个 null 都必须用 null 值来匹配
 1    @Test
 2    public void testFetchUsers() {
 3       when(namedParameterJdbcTemplate.query(eq(null),
 4           eq((MapSqlParameterSource)null),
 5           ArgumentMatchers.<RowMapper<String>>any()))
 6           .thenReturn(Arrays.asList("Hello", "Ketty"));
 7
 8        List<String> users = userDao.fetchUsers();
 9       assertThat(users).hasSize(2);
10    }

测试通过。eq((MapSqlParameterSource)null) 中的转型是为了避免准确调用重载方法。

最后就是一句话,Mockito 中的 any 不是真正的 any, any 不代表 null,有点类似 SQLServer 中的某个字段(c1) 的值为 null 时,where c1 is null 可以查出来,但是用 where c1 in (null) 就查不出来了。 永久链接 https://yanbin.blog/mockito-anystring-anyfoo-class-cannot-match-null-values/, 来自 隔叶黄莺 Yanbin's Blog
[版权声明] 本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。