Hamcrest equalTo比较GString与String内容匹配却返回false的解决方法
列表中存储了值类型为GString的参数,使用Hamcrest进行匹配校验时,即便二者作为普通字符串比较时内容完全一致,仍会返回String isn't GString的匹配失败结果。
期望实现Hamcrest在比较过程中自动将GString解析为普通String完成匹配,目前的临时规避方案是在将GString添加到列表时主动调用.toString()方法,但该方案侵入性较强,并不理想。
问题复现代码:
@Test def stackOverflow() { given: // values def v1 = '01' def v2 = '02' // a groovy string containing them def param = "${v1}:${v2}" // a matcher using a concrete string def matcher = Matchers.equalTo("01:02") when: // add actual into a list def mylist = [param] then: // check that actual Gstring matches expected String and fail that mylist, Matchers.hasItem(matcher) }
报错信息
Condition not satisfied: that mylist, Matchers.hasItem(matcher) | | | | [01:02] class org.hamcrest.Matchers false Expected: a collection containing "01:02" but: mismatches were: [was <01:02>]
匹配失败的核心原因是Hamcrest内置的Matchers.equalTo()依赖Java原生equals()方法做相等判断,而Groovy的GString和普通String的equals()实现自带强类型校验:只要比较双方一个是GStringImpl、一个是String,哪怕字符序列完全一致,equals()也会返回false,才会出现日志里打印内容完全相同但判定不匹配的现象。
方案1:自定义兼容GString的通用相等匹配器
一次定义即可全局复用,不需要修改业务侧构造参数的逻辑,比较前会自动做类型转换:
import org.hamcrest.Description import org.hamcrest.Matcher import org.hamcrest.TypeSafeMatcher class StringSafeMatcher extends TypeSafeMatcher<Object> { private final String expected StringSafeMatcher(Object expected) { this.expected = expected?.toString() } @Override protected boolean matchesSafely(Object actual) { return expected == actual?.toString() } @Override void describeTo(Description description) { description.appendText("string value equal to ").appendValue(expected) } @Override protected void describeMismatchSafely(Object actual, Description mismatchDescription) { mismatchDescription.appendText("was ").appendValue(actual?.toString()) } static Matcher<Object> equalToString(Object expected) { return new StringSafeMatcher(expected) } }
使用时直接替换原生equalTo即可:
def matcher = StringSafeMatcher.equalToString("01:02")
后续无论集合里存的是GString还是普通String,都会统一转成String做内容比较,不会再出现类型不匹配的问题。
方案2:使用Spock原生断言
从代码的given/when/then结构可以看出使用的是Spock测试框架,Spock本身的断言逻辑对GString做了兼容,不需要依赖Hamcrest就能完成校验:
then: // 校验列表包含目标字符串 mylist.contains("01:02") // 或者校验对应位置的值 mylist[0] == "01:02"
Groovy的==操作符和集合contains方法不会卡在GString和String的类型差异上,会自动比较字符序列内容,写法也更简洁。
不推荐方案
手动在每个GString加入集合前调用.toString(),这种方案需要在所有参数构造位置加转换逻辑,漏写就会复现问题,维护成本极高。
内容的提问来源于stack exchange,提问作者Peter Kahn

