LoginSignup
6
9

More than 1 year has passed since last update.

xUnitでExceptionをテストする

Last updated at Posted at 2022-08-14

はじめに

xUnitを使用してユニットテストを書いていくと、Exceptionをキャッチするテストを書くことがあります。
これまで、Assert.Throws を使うことが多かったのですが、腑に落ちにくいところがありました。

というのも、基本的に自分はテストはAAA (Arrange, Act, Assert)に分けて書くようにしていたのですが、このAssert.Throwsは果たしてどこに入るものなのか?という疑問がありました。

たとえばこれまでよくこんな感じで書いていました。

[Fact]
public void CustomerTest()
{
    var exception = Assert.Throws<ArgumentException>(() => 
        CreateCustomer(customerId, customerName, email));
    Assert.Equal("名前が入力されていません", exception.Message);
}

Assert.Throwsは AAA (Arange, Act, Assert) のどこに入る?

一般的にAAAの基本構造は以下のようなものです

[Fact]
public void CustomerTest(){
    // Arrange: 準備

    // Act: SUT(テスト対象)へのテストを実施

    // Assert: 結果の確認
}

では、Assert.Throwsは?
先ほどの例として挙げたコードを例にすると

[Fact]
public void CustomerTest()
{
    // Arrange
    string customerId = "id00001";
    string customerName = "";
    string email = "sample@example.com";

    // Act
    var exception = Assert.Throws<ArgumentException>(() => 
        CreateCustomer(customerId, customerName, email));

    // Assert
    Assert.Equal("名前が入力されていません", exception.Message);
}

つまり、Assert.ThrowsがActに記述されることになります。
でも、"Assert"と書いているので、もやもやするんです。気にならない人もいるとは思いますが、Actと書きながらAssertがあるというのが自分としては、なんだか気持ちが悪いです。

Assert.ThrowsをAssertに書く方法

2つの方法を紹介させていただきます。

1. Actionを使用する

Actionを使って、メソッドだけをActに書き、Assert.Throwsの引数に使用するというやり方です。
こんな感じになります。

[Fact]
public void CustomerTest()
{
    // Arrange
    string customerId = "id00001";
    string customerName = "";
    string email = "sample@example.com";

    // Act
    void Action() => CreateCustomer(customerId, customerName, email);

    // Assert
    var exception = Assert.Throws<ArgumentException>(Action)
    Assert.Equal("名前が入力されていません", exception.Message);
}

こんな風に、ActにはAction()を使用して分離させます。
ActにAction、AssertにAssert.Throwsが書かれていることになるので、見た感じでも分かりやすいように思います。
ちなみに戻り値のある場合は以下のようにFunction()を使用すればいいです。

    // Act
    Customer Function() => CreateCustomer(customer);

    // Assert
    var exception = Assert.Throws<ArgumentException>(Function)

2. Record.Exceptionを使用する方法

もう一つの方法はAssert.Throwsに似ているのですが、Exceptionを先に指定しない方法です。
ちなみにRecordというのはRecord型のことではなく、XUnitのRecordクラスです。
こんな感じになります

[Fact]
public void CustomerTest()
{
    // Arrange
    string customerId = "id00001";
    string customerName = "";
    string email = "sample@example.com";

    // Act
    var exception = Record.Exception(() => {
        CreateCustomer(customerId, customerName, email);
    }

    // Assert
    Assert.Equal(typeof(ArgumentNullException), exception.GetType());
    Assert.Equal("名前が入力されていません", exception.Message);
}

Actはメソッドに集中し、Record.Exceptionで例外をキャッチして、Assertでその例外を確認するということができます。コードからも明確に例外を確認するテストであることが分かります。

まとめ

いずれの場合でもテスト自体は成功しますが、テストは「何をテストしているのか明瞭である」ことが大切だと思います。
1つ目のActionの使用はパッと見は分かるのですが、ただメソッドの宣言だけで実際に実行しているのはAssert内となっています。それに対し2つ目のRecord.ExceptionはActで実行をしています。
そう思うと、最後のRecord.Excptionを使用する方法は、見た目も動作的にも一番しっくりくるように思いました。

6
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
9