Skip to content

AssertJ

Calvin Xiao edited this page Feb 4, 2014 · 17 revisions

Overview

AssertJ网站: http://joel-costigliola.github.io/assertj/

JUnit自己的Assert.assertEquals(String message, String expected, String actual); 是公认的烂API,你很难记住三个参数的位置。
所以后来收编了Hamcrest改成一个从左到右的读法,assertThat(actual, equalTo(expected)); 但实际写起来并不顺手。
所以后来又有了小三Fest Assertion上位,但它后来又不怎么更新了,最后fork了一个活跃的AssertJ出来,最新的代码是这样写的:

import static org.assertj.core.api.Assertions.*;

// common assertions
assertThat(yoda).isInstanceOf(Jedi.class);
assertThat(frodo.getName()).isEqualTo("Frodo");
assertThat(frodo).isNotEqualTo(sauron);
assertThat(frodo).isIn(fellowshipOfTheRing);
assertThat(sauron).isNotIn(fellowshipOfTheRing);

// String specific assertions
assertThat(frodo.getName()).startsWith("Fro").endsWith("do")
                           .isEqualToIgnoringCase("frodo");

// collection specific assertions
assertThat(fellowshipOfTheRing).hasSize(9)
                               .contains(frodo, sam)
                               .doesNotContain(sauron);

// map specific assertions (One ring and elves ring bearers initialized before)
assertThat(ringBearers).hasSize(4)
                       .contains(entry(Ring.oneRing, frodo), entry(Ring.nenya, galadriel))
                       .doesNotContain(entry(Ring.oneRing, aragorn));

// throwable specific assertions
try {
  fellowshipOfTheRing.get(9); 
  failBecauseExceptionWasNotThrown(IndexOutOfBoundsException.class);
} catch (Exception e) {
  assertThat(e).isInstanceOf(IndexOutOfBoundsException.class)
               .hasMessage("Index: 9, Size: 9")
               .hasNoCause();
}

可见,新的语法,可以更顺畅的从左往右读,可以用Fluent API连续的测试,而且对各种常见对象都有一些很方便的测试方法封装,包括Class, 基本对象, 基本类型, Collections, Map, Date, Exception, File,还有扩展模块支持Guava里的几种集合和Joda-time。

在Eclipse里的配置

要从Junit的assertEquals()方便的换成assertThat(),重要一点是你在代码里打完assertThat,Eclipse能自动提示你import static org.assertj.core.api.Assertions.*; 在Eclipse 点击 preferences > Java > Editor > Content assist > Favorites > New Type,加入org.assertj.core.api.Assertions,IntelliJ的方法类似。

另外,你应该已经配置了Eclipse的static import总是给* 而不是展开:preferences > Java > Code Style > Organize Import, 设置Number of static imports needed of * to 1。

批量替换JUnit Assert到AssertJ

官网上有批量转换Junit Assert的介绍,可以用它提供的Linux Shell脚本,也可以在Intellij里用正则表达式来替换,把assertEquals(expected, actual) 替换为assertThat(actual).isEqualTo(expected),及其它assertNull(), assertTrue()等的转换

在Eclipse上正则的语法略有不同,可参考下面的例子自行编写:

Search:  assertEquals\((.*), (.*)\);
Replace: assertThat\($2\).isEqualTo($1);

另外

没在前面例子里演示的技巧

  • 下一个版本,把打酱油的assertThat()可以简写成BDD风格的then(),更短。
  • as函数,加入错误描述,as要写在测试的前面。
assertThat(myList).as("list should be empty").isEmpty();
  • extracting函数,用字符串定义需要反射取出的属性/方法,比如遍历一个List,将里面每个对象的属性拿出来判断。当然,用名字反射永远的问题就是属性/函数改名的时候不能自动重构。
assertThat(userList).extracting("name").contains("calvin", "kevin");
  • Date的比较可以忽略时间上毫秒级的不同,只要是秒上的数值一样,或者绝对值相差在一秒内的就算对,比如第6.9秒与第7.3秒。类似的还有同一天,同一小时。
assertThat(date1).isEqualToIgnoringSeconds(date2);
assertThat(date1).isInSameSecondWindowAs(date2);
  • isXmlEqualTo忽略XML字符串之间换行缩进这种格式上的差别,只比较实际内容。issJsonEqualTo用JSON-Unit项目,也有类似fluent API。
assertThat(oneLineXml).isXmlEqualTo(expectedXml);
  • 两个Bean之间只比较某些特定的fields,而不是用equals()函数比较,isEqualToIgnoringGivenFields,isEqualToComparingOnlyGivenFields,isEqualToIgnoringNullFields 等,比如比较数据库里查出来的actual对象与这边厢new出来的expected对象,可能会少一些属性的时候就会用到。
  • File有hasContent(String expected),InputStream有hasContentEqualTo(InputStream expected),有或者是contenOf(),可以用上StringAssert里的函数。
File xFile = writeFile("xFile", "The Truth Is Out There");
assertThat(contentOf(xFile)).startsWith("The Truth").contains("Is Out");
  • 不在assert错误时立刻抛出异常,而是所有assert跑完后一次过抛出,免得fix完一个问题,再执行测试又才看到另一个。
  SoftAssertions softly = new SoftAssertions();
  softly.assertThat(mansion.guests()).as("Living Guests");
  softly.assertThat(mansion.kitchen()).as("Kitchen");
  softly.assertAll();