JUnit4での例外テストを楽チンにする！


> **2018/6/13追記**
> jUtaime は JUnit5 の登場によりその使命を終えました。
> 長らくご愛顧ありがとうございました m(_ _)m
> みなさま JUnit5 を使いましょう。

# What's the issue ?
JUnitでの例外検証コードをもっとスッキリ書きたいっ！
そう思っているのはきっと私だけではないはず・・・

# before
例えば `Person#setAge(int)` なんていうよくあるメソッドを作ったとして、次の4点を検証したい。

   * `setAge(-1)` は NG　→　`IllegalArgumentException` をスロー
   * `setAge(0)` は OK
   * `setAge(200)` は OK ： 超長寿社会の到来に備えて。
   * `setAge(201)` は NG　→　`IllegalArgumentException` をスロー、例外メッセージあり

通常のJUnit4ではこんな感じになると思います。

```java:JUnit4での通常の例外テスト
public class PersonTest {
    
    private Person person = new Person("John");
    
    @Rule
    public ExpectedException thrown = ExpectedException.none();
    
    @Test(expected = IllegalArgumentException.class)
    public void testSetAge1() {
        person.setAge(-1);
    }
    
    @Test
    public void testSetAge2() {
        try  {
            person.setAge(0);
            person.setAge(200);  // 200歳まではOKとする。
        } catch(RuntimeException e) {
            fail();
        }
    }
    
    @Test
    public void testSetAge3() {
        thrown.expect(IllegalArgumentException.class);
        thrown.expectMessage("Is he a zombie!?");
        
        person.setAge(201);
    }
}
```

なーんでたったこれっぽっちの検証で、こんなにもっさりしたコードを書かなきゃいけないんですかね！ ぷんぷん

# after
これで良いじゃん...

```java:こう書きたい
public class PersonTest {
    
    @Test
    public void testSetAge() {
        Person p = new Person("John");
        assertThat(of(() -> p.setAge(-1)),  raise(IllegalArgumentException.class));
        assertThat(of(() -> p.setAge(0)),   raiseNothing());
        assertThat(of(() -> p.setAge(200)), raiseNothing());
        assertThat(of(() -> p.setAge(201)), raise(IllegalArgumentException.class, "Is he a zombie!?"));
    }
}
```

# so that
というわけで、afterを実現するライブラリを作りました。
~~**nmby/jetaime** · GitHub~~
[**nmby/jUtaime** · GitHub](https://github.com/nmby/jUtaime) （2015/7/26 移動しました。）

# usage

* 例外をスローしうる検証対象の処理を、`Testee.of()` の中に記述します。  
* 期待する例外の型やメッセージ、原因（cause）の型などを、`RaiseMatchers.raise()` などで指定します。

```java:
assertThat(Testee.of(SomeClass::someMethodShouldFail),
        RaiseMatchers.raise(SomeException.class, "expected message"));
```

`Testee` と `RaiseMatchers` は static インポートしておくとよいでしょう。
次のような色々な書き方が可能です。

```java:
assertThat(of(() -> Integer.valueOf("abc")), raiseExact(NumberFormatException.class));
assertThat(of(() -> Integer.valueOf("123")), raiseNothing());
assertThat(of(() -> { Object o = null; o.toString(); }), raise(RuntimeException.class));
assertThat(of(obj::dbOperation), rootCause(IOException.class));
```
また、他の Matcher と組み合わせて使用することもできます。
次の例では、[hamcrest.org](http://hamcrest.org/JavaHamcrest/) が提供する `anyOf`、`allOf`、`not` と組み合わせて使用しています。

```java:
// NullPointerException または IllegalArgumentException がスローされることを検証
assertThat(of(() -> obj.someOperation(null)),
        anyOf(raise(NullPointerException.class), raise(IllegalArgumentException.class)));

// NullPointerException 以外の何らかの実行時例外を原因として上位例外がスローされることを検証
assertThat(of(() -> obj.someOperation(param)),
        allOf(raise(WrappingException.class, "expected message"),
                rootCause(RuntimeException.class),
                not(rootCause(NullPointerException.class))));
```
`allOf()` の代わりに、次の連結スタイルで記述することも可能です。

```java:
// NullPointerException 以外の何らかの実行時例外を原因として上位例外がスローされることを検証
assertThat(of(() -> obj.someOperation(param)),
        raise(WrappingException.class, "expected message")
                .rootCause(RuntimeException.class)
                .not(rootCause(NullPointerException.class)));
```

詳しくは [javadoc](http://jutaime.hotchpotch.xyz/docs/api/index.html) を見てね♪


なお、eclipseで使えばちゃんとレポートも表示されます。
![JUnitTraceView.png](https://qiita-image-store.s3.amazonaws.com/0/84939/856ef419-6168-7aa1-51ee-65ccb35d623f.png)

# dependencies
* java 8 以上が必要です。
* [hamcrest-core](http://search.maven.org/#search%7Cga%7C1%7Cg%3Aorg.hamcrest) も必要ですが、JUnit4を使ってるならすでに入ってるはずです。