JUnit 4テストで特定の例外がスローされることをどのようにアサートしますか?
質問
JUnit4を慣用的に使用して、一部のコードが例外をスローすることをテストするにはどうすればよいですか?
確かに次のようなことができますが:
@Test
public void testFooThrowsIndexOutOfBoundsException() {
boolean thrown = false;
try {
foo.doStuff();
} catch (IndexOutOfBoundsException e) {
thrown = true;
}
assertTrue(thrown);
}
これらの種類の状況に対して、JUnitの精神がはるかに少なく、より精神的なアノテーションまたはAssert.xyzまたは何かがあることを思い出します。
解決
JUnit 4はこれをサポートしています:
@Test(expected = IndexOutOfBoundsException.class)
public void testIndexOutOfBoundsException() {
ArrayList emptyList = new ArrayList();
Object o = emptyList.get(0);
}
他のヒント
編集 JUnit5がリリースされたので、最良のオプションは Assertions.assertThrows()
を使用することです(他の回答)。
JUnit 5に移行していないが、JUnit 4.7を使用できる場合、 ExpectedException
ルール:
public class FooTest {
@Rule
public final ExpectedException exception = ExpectedException.none();
@Test
public void doStuffThrowsIndexOutOfBoundsException() {
Foo foo = new Foo();
exception.expect(IndexOutOfBoundsException.class);
foo.doStuff();
}
}
foo.doStuff()<の前に
IndexOutOfBoundsException
がスローされるとテストが失敗するため、これは @Test(expected = IndexOutOfBoundsException.class)
よりもはるかに優れています/ code>
詳細については、この記事をご覧ください
予期される例外の使用には注意してください。テストでは、特定のコード行ではなく、メソッドが例外をスローしたことをアサートするだけです。
パラメータ検証のテストにこれを使用する傾向があります。そのようなメソッドは通常非常に単純であるためです。
try {
methodThatShouldThrow();
fail( "My method didn't throw when I expected it to" );
} catch (MyException expectedException) {
}
判断を適用します。
前に回答したように、JUnitで例外を処理する方法はたくさんあります。しかし、Java 8にはもう1つあります:Lambda Expressionsを使用することです。 Lambda Expressionsを使用すると、次のような構文を実現できます。
@Test
public void verifiesTypeAndMessage() {
assertThrown(new DummyService()::someMethod)
.isInstanceOf(RuntimeException.class)
.hasMessage("Runtime exception occurred")
.hasMessageStartingWith("Runtime")
.hasMessageEndingWith("occurred")
.hasMessageContaining("exception")
.hasNoCause();
}
assertThrownは、ラムダ式、メソッド参照、またはコンストラクター参照を使用してインスタンスを作成できる機能インターフェイスを受け入れます。インターフェースを受け入れるassertThrownは、例外を予期し、例外を処理する準備ができています。
これは比較的単純でありながら強力な手法です。
この手法について説明しているこのブログ投稿をご覧ください: http://blog.codeleak.pl/2014/07/junit-testing-exception-with-java-8-and-lambda-expressions.html
ソースコードは次の場所にあります: https://github.com/kolorobot/unit-testing-demo/tree/master/src/test/java/com/github/kolorobot/exceptions/java8
開示:私はブログとプロジェクトの著者です。
junitでは、例外をテストする4つの方法があります。
-
junit4.xでは、テスト注釈のオプションの 'expected'属性を使用します
@Test(expected = IndexOutOfBoundsException.class) public void testFooThrowsIndexOutOfBoundsException() { foo.doStuff(); }
-
junit4.xの場合、ExpectedExceptionルールを使用します
public class XxxTest { @Rule public ExpectedException thrown = ExpectedException.none(); @Test public void testFooThrowsIndexOutOfBoundsException() { thrown.expect(IndexOutOfBoundsException.class) //you can test the exception message like thrown.expectMessage("expected messages"); foo.doStuff(); } }
-
junit 3フレームワークで広く使用されている古典的なtry / catch方法も使用できます
@Test public void testFooThrowsIndexOutOfBoundsException() { try { foo.doStuff(); fail("expected exception was not occured."); } catch(IndexOutOfBoundsException e) { //if execution reaches here, //it indicates this exception was occured. //so we need not handle it. } }
-
最後に、junit5.xでは、次のようにassertThrowsを使用することもできます
@Test public void testFooThrowsIndexOutOfBoundsException() { Throwable exception = assertThrows(IndexOutOfBoundsException.class, () -> foo.doStuff()); assertEquals("expected messages", exception.getMessage()); }
-
so
- 第1の方法は、例外のタイプのみをテストする場合に使用されます
- さらに例外メッセージをテストする場合は、他の3つの方法が使用されます
- junit 3を使用する場合、3番目のものが優先されます
- junit 5が好きなら、4番目のものが好きです
-
詳細については、このドキュメントをご覧ください。詳細については junit5ユーザーガイドをご覧ください。
tl; dr
-
JDK8より前:古い
try
-catch
ブロックをお勧めします。 (catch
ブロックの前にfail()
アサーションを追加することを忘れないでください) -
post-JDK8:AssertJまたはカスタムラムダを使用して、例外的動作をアサートします。
Junit 4またはJUnit 5に関係なく
長い物語
自分で行う try
- catch
ブロックを作成するか、JUnitツール( @Test( expected = ...)
または @Rule ExpectedException
JUnitルール機能)。
しかし、これらの方法はそれほどエレガントではなく、他のツールと読みやすさをうまく組み合わせません。さらに、JUnitツールには落とし穴があります。
-
try
-catch
ブロックでは、テストされた動作に関連するブロックを記述し、catchブロックにアサーションを記述する必要があります。多くの場合、このスタイルはテストの読み取りフローを中断します。また、try
ブロックの最後にAssert.fail
を記述する必要があります。そうしないと、テストがアサーションの片側を見逃す可能性があります。 PMD 、 findbugs 、またはソナーがこのような問題を発見します。 -
@Test(expected = ...)
機能は興味深いものです。コードを書く回数が減り、このテストを書くことでコーディングエラーが発生しにくくなるためです。 しかしこのアプローチにはいくつかの領域が欠けています。- テストが原因やメッセージなど、例外に関する追加事項を確認する必要がある場合(適切な例外メッセージは本当に重要であり、正確な例外タイプを持っているだけでは不十分な場合があります)。
-
また、メソッドに期待が置かれているため、テストされたコードの記述方法に応じて、テストコードの間違った部分が例外をスローし、誤検知テストにつながる可能性があり、< em> PMD 、 findbugs または Sonar は、そのようなコードに関するヒントを提供します。
@Test(expected = WantedException.class) public void call2_should_throw_a_WantedException__not_call1() { // init tested tested.call1(); // may throw a WantedException // call to be actually tested tested.call2(); // the call that is supposed to raise an exception }
-
ExpectedException
ルールは、以前の警告を修正する試みでもありますが、期待スタイルの EasyMock ユーザーを使用しているため、少し使いにくいと感じていますこのスタイルをよく知っています。一部の人にとっては便利かもしれませんが、行動主導開発(BDD)またはアクトアクトアサート(AAA)の原則に従うと、ExpectedException
ルールが勝ちますそれらの書き方に合わない。それ以外に、期待する場所によっては、@Test
の方法と同じ問題が発生する可能性があります。@Rule ExpectedException thrown = ExpectedException.none() @Test public void call2_should_throw_a_WantedException__not_call1() { // expectations thrown.expect(WantedException.class); thrown.expectMessage("boom"); // init tested tested.call1(); // may throw a WantedException // call to be actually tested tested.call2(); // the call that is supposed to raise an exception }
予想される例外がテスト文の前に置かれていても、テストがBDDまたはAAAに従う場合、読み取りフローが中断されます。
著者のJUnitに関するコメントの問題も参照してください。
ExpectedException
の。 JUnit 4.13-beta-2 はこのメカニズムを非推奨にしています:プルリクエスト#1519 :ExpectedExceptionを非推奨
Assert.assertThrowsメソッドは、例外を検証するためのより良い方法を提供します。さらに、TestWatcherなどの他のルールでExpectedExceptionを使用するとエラーが発生しやすくなります。その場合、ルールの順序が重要になるためです。
これらの上記のオプションにはすべての注意事項があり、明らかにコーダーエラーの影響を受けません。
-
この回答を作成してから有望に思えるプロジェクトがあります。それは catchです-exception 。
プロジェクトの説明にあるように、コーダーは
JUnit 5がリリースされたので、最良のオプションは Assertions.assertThrows()
を使用することです(参照
Junit 5ユーザーガイド)。
例外がスローされたことを確認し、真実を使用して、例外メッセージ:
public class FooTest {
@Test
public void doStuffThrowsIndexOutOfBoundsException() {
Foo foo = new Foo();
IndexOutOfBoundsException e = assertThrows(
IndexOutOfBoundsException.class, foo::doStuff);
assertThat(e).hasMessageThat().contains("woops!");
}
}
他の回答のアプローチに対する利点は次のとおりです。
- JUnitに組み込み
- ラムダ内のコードが例外をスローしない場合は有用な例外メッセージを受け取り、別の例外をスローする場合はスタックトレースを受け取ります
- 簡潔
- テストがArrange-Act-Assertに従うことを許可します
- 例外をスローする予定のコードを正確に示すことができます
- 予想される例外を
throws
句にリストする必要はありません - 選択したアサーションフレームワークを使用して、キャッチされた例外に関するアサーションを作成できます
同様のメソッドがJUnit 4.13の org.junit Assert
に追加されます。
これについてはどうですか:非常に一般的な例外をキャッチし、それがcatchブロックから抜けていることを確認してから、例外のクラスが予想どおりであることをアサートします。このアサートは、a)例外のタイプが間違っている場合(たとえば、代わりにNullポインターを取得した場合)、b)例外がスローされなかった場合に失敗します。
public void testFooThrowsIndexOutOfBoundsException() {
Throwable e = null;
try {
foo.doStuff();
} catch (Throwable ex) {
e = ex;
}
assertTrue(e instanceof IndexOutOfBoundsException);
}
AssertJ アサーションを使用します。これはJUnitと一緒に使用できます:
import static org.assertj.core.api.Assertions.*;
@Test
public void testFooThrowsIndexOutOfBoundsException() {
Foo foo = new Foo();
assertThatThrownBy(() -> foo.doStuff())
.isInstanceOf(IndexOutOfBoundsException.class);
}
@Test(expected = IndexOutOfBoundsException.class)
よりも優れています。これは、テストで予期される行が例外をスローすることを保証し、メッセージなどの例外に関する詳細を簡単に確認できるためです。
assertThatThrownBy(() ->
{
throw new Exception("boom!");
})
.isInstanceOf(Exception.class)
.hasMessageContaining("boom");
同じ問題を解決するために、私は小さなプロジェクトをセットアップしました。 http://code.google.com/p/catch-exception/
この小さなヘルパーを使用して作成します
verifyException(foo, IndexOutOfBoundsException.class).doStuff();
これは、JUnit 4.7のExpectedExceptionルールほど冗長ではありません。 skaffmanが提供するソリューションと比較して、例外を予想するコード行を指定できます。これがお役に立てば幸いです。
更新: JUnit5では、例外テストの改善が行われています: assertThrows
。
次の例は、 Junit 5ユーザーガイド
@Test
void exceptionTesting() {
Throwable exception = assertThrows(IllegalArgumentException.class, () ->
{
throw new IllegalArgumentException("a message");
});
assertEquals("a message", exception.getMessage());
}
JUnit 4を使用したオリジナルの回答
例外がスローされることをテストする方法はいくつかあります。また、投稿 JUnitで優れた単体テストを作成する方法
で、以下のオプションについても説明しました。 expected
パラメータ @Test(expected = FileNotFoundException.class)
を設定します。
@Test(expected = FileNotFoundException.class)
public void testReadFile() {
myClass.readFile("test.txt");
}
try
catch
public void testReadFile() {
try {
myClass.readFile("test.txt");
fail("Expected a FileNotFoundException to be thrown");
} catch (FileNotFoundException e) {
assertThat(e.getMessage(), is("The file test.txt does not exist!"));
}
}
ExpectedException
ルールを使用したテスト。
@Rule
public ExpectedException thrown = ExpectedException.none();
@Test
public void testReadFile() throws FileNotFoundException {
thrown.expect(FileNotFoundException.class);
thrown.expectMessage(startsWith("The file test.txt"));
myClass.readFile("test.txt");
}
これを行うこともできます:
@Test
public void testFooThrowsIndexOutOfBoundsException() {
try {
foo.doStuff();
assert false;
} catch (IndexOutOfBoundsException e) {
assert true;
}
}
私見、JUnitで例外をチェックする最良の方法は、try / catch / fail / assertパターンです:
// this try block should be as small as possible,
// as you want to make sure you only catch exceptions from your code
try {
sut.doThing();
fail(); // fail if this does not throw any exception
} catch(MyException e) { // only catch the exception you expect,
// otherwise you may catch an exception for a dependency unexpectedly
// a strong assertion on the message,
// in case the exception comes from anywhere an unexpected line of code,
// especially important if your checking IllegalArgumentExceptions
assertEquals("the message I get", e.getMessage());
}
assertTrue
は一部の人にとって少し強い場合があるため、 assertThat(e.getMessage()、containsString(&quot; themessage&quot;);
が望ましい場合があります。
JUnit 5ソリューション
@Test
void testFooThrowsIndexOutOfBoundsException() {
Throwable exception = expectThrows( IndexOutOfBoundsException.class, foo::doStuff );
assertEquals( "some message", exception.getMessage() );
}
http://junit.org/junit5/のJUnit 5に関する詳細情報docs / current / user-guide /#writing-tests-assertions
ここで多くの方法を試しましたが、それらは複雑であるか、要件を完全には満たしていませんでした。実際、ヘルパーメソッドは非常に簡単に記述できます。
public class ExceptionAssertions {
public static void assertException(BlastContainer blastContainer ) {
boolean caughtException = false;
try {
blastContainer.test();
} catch( Exception e ) {
caughtException = true;
}
if( !caughtException ) {
throw new AssertionFailedError("exception expected to be thrown, but was not");
}
}
public static interface BlastContainer {
public void test() throws Exception;
}
}
次のように使用します:
assertException(new BlastContainer() {
@Override
public void test() throws Exception {
doSomethingThatShouldExceptHere();
}
});
ゼロの依存関係:mockitoやpowermockは不要です。最終クラスでも問題なく動作します。
Mkyongブログ。 @Rule
アノテーションを使用した try / catch
の柔軟性があります。カスタマイズされた例外の特定の属性を読み取ることができるため、このアプローチが気に入っています。
package com.mkyong;
import com.mkyong.examples.CustomerService;
import com.mkyong.examples.exception.NameNotFoundException;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.Matchers.hasProperty;
public class Exception3Test {
@Rule
public ExpectedException thrown = ExpectedException.none();
@Test
public void testNameNotFoundException() throws NameNotFoundException {
//test specific type of exception
thrown.expect(NameNotFoundException.class);
//test message
thrown.expectMessage(is("Name is empty!"));
//test detail
thrown.expect(hasProperty("errCode")); //make sure getters n setters are defined.
thrown.expect(hasProperty("errCode", is(666)));
CustomerService cust = new CustomerService();
cust.findByName("");
}
}
Java 8ソリューション
次のようなソリューションが必要な場合:
- Java 8ラムダを利用します
- JUnitの魔法に依存しない
- 1つのテストメソッド内で複数の例外を確認できます
- テストメソッド全体の未知の行ではなく、テストメソッド内の特定の行セットによってスローされる例外をチェックします
- さらに調査できるように、スローされた実際の例外オブジェクトを生成します
ここに、私が書いたユーティリティ関数を示します。
public final <T extends Throwable> T expectException( Class<T> exceptionClass, Runnable runnable )
{
try
{
runnable.run();
}
catch( Throwable throwable )
{
if( throwable instanceof AssertionError && throwable.getCause() != null )
throwable = throwable.getCause(); //allows "assert x != null : new IllegalArgumentException();"
assert exceptionClass.isInstance( throwable ) : throwable; //exception of the wrong kind was thrown.
assert throwable.getClass() == exceptionClass : throwable; //exception thrown was a subclass, but not the exact class, expected.
@SuppressWarnings( "unchecked" )
T result = (T)throwable;
return result;
}
assert false; //expected exception was not thrown.
return null; //to keep the compiler happy.
}
>
次のように使用します:
@Test
public void testThrows()
{
RuntimeException e = expectException( RuntimeException.class, () ->
{
throw new RuntimeException( "fail!" );
} );
assert e.getMessage().equals( "fail!" );
}
JUnitにはこれに対する組み込みのサポートがあり、&quot; expected&quot;属性
私の場合、dbからRuntimeExceptionを常に取得しますが、メッセージは異なります。また、例外をそれぞれ処理する必要があります。テスト方法は次のとおりです。
@Test
public void testThrowsExceptionWhenWrongSku() {
// Given
String articleSimpleSku = "999-999";
int amountOfTransactions = 1;
Exception exception = null;
// When
try {
createNInboundTransactionsForSku(amountOfTransactions, articleSimpleSku);
} catch (RuntimeException e) {
exception = e;
}
// Then
shouldValidateThrowsExceptionWithMessage(exception, MESSAGE_NON_EXISTENT_SKU);
}
private void shouldValidateThrowsExceptionWithMessage(final Exception e, final String message) {
assertNotNull(e);
assertTrue(e.getMessage().contains(message));
}
次のように、オンとオフを切り替えることができるマッチャーを作成します。
public class ExceptionMatcher extends BaseMatcher<Throwable> {
private boolean active = true;
private Class<? extends Throwable> throwable;
public ExceptionMatcher(Class<? extends Throwable> throwable) {
this.throwable = throwable;
}
public void on() {
this.active = true;
}
public void off() {
this.active = false;
}
@Override
public boolean matches(Object object) {
return active && throwable.isAssignableFrom(object.getClass());
}
@Override
public void describeTo(Description description) {
description.appendText("not the covered exception type");
}
}
使用するには:
add public ExpectedException exception = ExpectedException.none();
、
その後:
ExceptionMatcher exMatch = new ExceptionMatcher(MyException.class);
exception.expect(exMatch);
someObject.somethingThatThrowsMyException();
exMatch.off();
JUnit 4以降では、次のように例外をテストできます
@Rule
public ExpectedException exceptions = ExpectedException.none();
これにより、JUnitテストの改善に使用できる多くの機能が提供されます。
以下の例をご覧になった場合、例外について3つのことをテストしています。
- スローされた例外のタイプ
- 例外メッセージ
- 例外の原因
public class MyTest {
@Rule
public ExpectedException exceptions = ExpectedException.none();
ClassUnderTest classUnderTest;
@Before
public void setUp() throws Exception {
classUnderTest = new ClassUnderTest();
}
@Test
public void testAppleisSweetAndRed() throws Exception {
exceptions.expect(Exception.class);
exceptions.expectMessage("this is the exception message");
exceptions.expectCause(Matchers.<Throwable>equalTo(exceptionCause));
classUnderTest.methodUnderTest("param1", "param2");
}
}
例外を返す必要のあるメソッドの後にアサーション失敗を使用できます:
try{
methodThatThrowMyException();
Assert.fail("MyException is not thrown !");
} catch (final Exception exception) {
// Verify if the thrown exception is instance of MyException, otherwise throws an assert failure
assertTrue(exception instanceof MyException, "An exception other than MyException is thrown !");
// In case of verifying the error message
MyException myException = (MyException) exception;
assertEquals("EXPECTED ERROR MESSAGE", myException.getMessage());
}
NamShubWriter が言ったことに加えて、次のことを確認してください:
- ExpectedExceptionインスタンスは public (関連する質問)
- ExpectedExceptionは、 @Beforeメソッドなどではインスタンス化されません。この投稿は、すべての複雑さを明確に説明していますJUnitの実行順序。
これを行うしない:
@Rule
public ExpectedException expectedException;
@Before
public void setup()
{
expectedException = ExpectedException.none();
}
最後に、 this ブログ投稿では、特定の例外がスローされたことをアサートする方法を明確に示しています。
junitテストで例外を処理するには、ライブラリ assertj-core
をお勧めします
Java 8では、次のようになります。
//given
//when
Throwable throwable = catchThrowable(() -> anyService.anyMethod(object));
//then
AnyException anyException = (AnyException) throwable;
assertThat(anyException.getMessage()).isEqualTo("........");
assertThat(exception.getCode()).isEqualTo(".......);
Java8を使用したJunit4ソリューションでは、次の関数を使用します。
public Throwable assertThrows(Class<? extends Throwable> expectedException, java.util.concurrent.Callable<?> funky) {
try {
funky.call();
} catch (Throwable e) {
if (expectedException.isInstance(e)) {
return e;
}
throw new AssertionError(
String.format("Expected [%s] to be thrown, but was [%s]", expectedException, e));
}
throw new AssertionError(
String.format("Expected [%s] to be thrown, but nothing was thrown.", expectedException));
}
使用法は次のとおりです。
assertThrows(ValidationException.class,
() -> finalObject.checkSomething(null));
唯一の制限は、ラムダ式で final
オブジェクト参照を使用することです。
このソリューションでは、 @Test(expected = IndexOutOfBoundsException.class)
ソリューションを使用して、メソッドレベルでの調整可能性を期待する代わりに、テストアサーションを継続できます。
たとえば、下記のコードフラグメントのJunitを記述したい場合
public int divideByZeroDemo(int a,int b){
return a/b;
}
public void exceptionWithMessage(String [] arr){
throw new ArrayIndexOutOfBoundsException("Array is out of bound");
}
上記のコードは、発生する可能性のあるいくつかの未知の例外をテストするためのものであり、以下のコードは、カスタムメッセージで例外をアサートするためのものです。
@Rule
public ExpectedException exception=ExpectedException.none();
private Demo demo;
@Before
public void setup(){
demo=new Demo();
}
@Test(expected=ArithmeticException.class)
public void testIfItThrowsAnyException() {
demo.divideByZeroDemo(5, 0);
}
@Test
public void testExceptionWithMessage(){
exception.expectMessage("Array is out of bound");
exception.expect(ArrayIndexOutOfBoundsException.class);
demo.exceptionWithMessage(new String[]{"This","is","a","demo"});
}
Java 8では、チェックするコードと予期される例外をパラメーターとして取るメソッドを作成できます。
private void expectException(Runnable r, Class<?> clazz) {
try {
r.run();
fail("Expected: " + clazz.getSimpleName() + " but not thrown");
} catch (Exception e) {
if (!clazz.isInstance(e)) fail("Expected: " + clazz.getSimpleName() + " but " + e.getClass().getSimpleName() + " found", e);
}
}
そしてテスト内:
expectException(() -> list.sublist(0, 2).get(2), IndexOutOfBoundsException.class);
利点:
- ライブラリに依存しない
- ローカライズされたチェック-より正確で、必要に応じて1つのテスト内でこのような複数のアサーションを持つことができます
- 使いやすい
Java 8ラムダを使用した私のソリューション:
public static <T extends Throwable> T assertThrows(Class<T> expected, ThrowingRunnable action) throws Throwable {
try {
action.run();
Assert.fail("Did not throw expected " + expected.getSimpleName());
return null; // never actually
} catch (Throwable actual) {
if (!expected.isAssignableFrom(actual.getClass())) { // runtime '!(actual instanceof expected)'
System.err.println("Threw " + actual.getClass().getSimpleName()
+ ", which is not a subtype of expected "
+ expected.getSimpleName());
throw actual; // throw the unexpected Throwable for maximum transparency
} else {
return (T) actual; // return the expected Throwable for further examination
}
}
}
Runable は必要な throws
を宣言しないため、FunctionalInterfaceを定義する必要があります。
@FunctionalInterface
public interface ThrowingRunnable {
void run() throws Throwable;
}
メソッドは次のように使用できます:
class CustomException extends Exception {
public final String message;
public CustomException(final String message) { this.message = message;}
}
CustomException e = assertThrows(CustomException.class, () -> {
throw new CustomException("Lorem Ipsum");
});
assertEquals("Lorem Ipsum", e.message);
テストケースを記述する方法は2つあります
- メソッドによってスローされる例外でテストに注釈を付けます。このような
@Test(expected = IndexOutOfBoundsException.class)
-
try catchブロックを使用してテストクラスで例外を簡単にキャッチし、テストクラスのメソッドからスローされたメッセージをアサートできます。
try{ } catch(exception to be thrown from method e) { assertEquals("message", e.getmessage()); }
これがあなたの質問に答えることを望みます 幸せな学習...