【C# .NET】Fluent AssertionsのAssertionメソッドについて整理する
概要
Fluent Assertions
は、C# .NETでテストコードを書く際に利用されるライブラリです。テストの検証部分を英語の文章のように直感的かつ読みやすく記述できます。たとえば、「この値が期待通りである」や「この例外がスローされた」といったことを、簡潔で自然な形で表現可能です。
「なぜFluent Assertions
を使うのか?」については、公式ドキュメントのWhyに分かりやすく書かれていますので、そちらをご覧いただければと思います。
この記事では、
- 私が公式ドキュメントを読んでもすぐに理解できなかった
Assertion
メソッド - 私が「へー、こんなのもあるんだなー」と感じた
Assertion
メソッド
を中心に整理していきます。
Fluent Assertionsをインストールする
$ dotnet add package FluentAssertions
以後、.NET
やFluentAssertions
のバージョンは以下のとおりです。
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="FluentAssertions" Version="7.0.0" />
</ItemGroup>
</Project>
Nullable
HaveValue / NotBeNull
値を持っているかを検証するのがHaveValue
、null
でないことを検証するのがNotBeNull
です。
int? theInt = 3;
theInt.Should().HaveValue(); // パスする
theInt.Should().NotBeNull(); // パスする
int? theInt = null;
theInt.Should().NotHaveValue(); // パスする
theInt.Should().BeNull(); // パスする
default
値が設定された場合、HaveValue
もNotBeNull
も値なしの扱いとなり、テストが失敗します。default値が指定された場合、NotBeNull
は成功しそうな気もしますが、NotBeNull
もテストは通りません。
int? theInt = default;
// 失敗 Expected a value.
theInt.Should().HaveValue();
// 失敗 Expected a value.
theInt.Should().NotBeNull();
HaveValueとNotBeNullの違い
HaveValue
とNotBeNull
の主な違いは、対象型 と 使用目的 です。
HaveValue
はNullable<T>
型に限定されますが、NotBeNull
はNullable<T>
型に加えて参照型にも使えます。
string theString = "aaa";
int? theNullableInt = 33;
// ⭕️ NotBeNullは、参照型で使用できる
theString.Should().NotBeNull();
// ⭕️ HaveValueは、Nullable<T>で使用できる
theNullableInt.Should().HaveValue();
// ❌ HaveValueは、参照型では使用できない
theString.Should().HaveValue();
-
NotBeNull
: 値がnull
でないことを確認するときに使う -
HaveValue
: 値を持っていることを確認するときに使う
実際のところ、HaveValue
とNotBeNull
の挙動は全く一緒ですが、検証意図に応じてHaveValue
とNotBeNull
を使い分けることで、テストの可読性向上に繋がります。
文字列
BeEmpty / BeNullOrWhiteSpace
BeEmpty(NotBeEmpty)
は空文字とnullを明確に区別します。
string theString = "";
string theString2 = String.Empty;
string theString3 = null!;
// パスする
theString.Should().BeEmpty();
// パスする
theString2.Should().BeEmpty();
// 失敗 Expected theString3 to be empty, but found <null>.
// BeEmptyは空文字を期待し、nullを許容しないため失敗
theString3.Should().BeEmpty();
// パスする
theString3.Should().NotBeEmpty();
null
と空文字をセットでチェックしたい場合はBeNullOrWhiteSpace(NotBeNullOrWhiteSpace)
が使えます。
string theString = "";
string theString2 = String.Empty;
string theString3 = null!;
// パスする
theString.Should().BeNullOrWhiteSpace();
// パスする
theString2.Should().BeNullOrWhiteSpace();
// パスする
theString3.Should().BeNullOrWhiteSpace();
// 失敗 Expected theString not to be <null> or whitespace, but found "".
theString.Should().NotBeNullOrWhiteSpace();
// 失敗 Expected theString not to be <null> or whitespace, but found "".
theString2.Should().NotBeNullOrWhiteSpace();
// 失敗 Expected theString3 not to be <null> or whitespace, but found <null>.
theString3.Should().NotBeNullOrWhiteSpace();
Be / BeEquivalentTo
Be
は大文字小文字を区別して比較します。一方で、BeEquivalentTo
は大文字小文字を区別せずに比較します。
string theString = "This is a String";
// 失敗 Expected theString to be "THIS IS A STRING", but "This is a String" differs near "his" (index 1).
theString.Should().Be("THIS IS A STRING");
// パスする
theString.Should().BeEquivalentTo("THIS IS A STRING");
Contain / ContainEquivalentOf
ContainEquivalentOf
も同様に大文字小文字を区別せずに比較します。また、Contain
系のAssertionメソッドは、対象文字列が含まれる回数まで検証可能です。
string theString = "This is a String";
theString.Should().Contain("is a"); // パスする
theString.Should().Contain("is a", Exactly.Once()); // パスする
// 失敗 Expected theString "This is a String" to contain "is a" at least 2 times, but found it 1 time.
theString.Should().Contain("is a", AtLeast.Twice());
theString.Should().ContainEquivalentOf("IS A"); // パスする
Collections
NotBeEmpty / NotBeNullOrEmpty
Collectionが存在することの確認は、NotBeEmpty
やNotBeNullOrEmpty
が使えます。いずれも、null
か空
の場合に失敗します。挙動は同じですが、コレクションがnull
の可能性がある場合はNotBeNullOrEmpty
を使い、null
を想定しない場合はNotBeEmpty
を使うのが良いと思います。これによって、何を検証するかの意図が明確になります。
IEnumerable<int> collection1 = new[] { 1, 2, 5, 8 };
collection1.Should().NotBeEmpty(); // パス
collection1.Should().NotBeNullOrEmpty(); // パス
IEnumerable<int> collection2 = new List<int>();
// 失敗 Expected collection2 not to be empty.
collection2.Should().NotBeEmpty();
// 失敗 Expected collection2 not to be empty.
collection2.Should().NotBeNullOrEmpty();
IEnumerable<int> collection3 = null!;
// 失敗 Expected collection3 not to be empty, but found <null>.
collection3.Should().NotBeEmpty();
// 失敗 Expected collection3 not to be <null>.
collection3.Should().NotBeNullOrEmpty();
Equal / BeEquivalentTo
次は、Equal
とBeEquivalentTo
です。いずれもコレクションの要素一致を検証するメソッドとなります。
使い分けとしては、
- 順序まで厳密に比較したい場合は
Equal
- 順序関係なく要素が一致のみで良い場合は
BeEquivalentTo
とすれば良さそうです。
IEnumerable<int> collection = new[] { 1, 2, 5, 8 };
collection.Should().Equal(new List<int> { 1, 2, 5, 8 }); // パスする
collection.Should().Equal(1, 2, 5, 8); // パスする
// BeEquivalentToは順序関係なく要素が一致しているかを検証する
collection.Should().BeEquivalentTo(new[] {8, 2, 1, 5}); // パスする
// BeEquivalentToには順序の厳密比較をするオプションがある
collection.Should().BeEquivalentTo(new List<int> {8, 2, 1, 5}, options => options.WithStrictOrdering());
// 失敗 Expected collection[0] to be 8, but found 1. Expected collection[2] to be 1, but found 5. Expected collection[3] to be 5, but found 8.
HaveCount
要素数の検証にはHaveCount
が使えます。
HaveCount
以外はそこまで使用頻度は多くなさそうですが、HaveSameCount
は存在自体初めて知ったので載せておきます。
IEnumerable<int> collection = new[] { 1, 2, 5, 8 };
collection.Should().HaveCount(4); // パスする
collection.Should().HaveCountGreaterThan(3); // パスする
collection.Should().HaveCountGreaterThanOrEqualTo(4); // パスする
// 指定のコレクションと要素数が同じかを検証する
collection.Should().HaveSameCount(new[] { 6, 2, 0, 5 }); // パスする
BeSubsetOf
BeSubsetOf
は、コレクションが指定されたスーパーセットの部分集合であることを検証するメソッドです。
IEnumerable<int> collection1 = new[] { 1, 2, 5, 8 };
IEnumerable<int> collection2 = new[] { 1, 2, 5, 98 };
// パスする
collection1.Should().BeSubsetOf(new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, });
// 失敗 Expected collection2 to be a subset of {1, 2, 3, 4, 5, 6, 7, 8, 9}, but items {98} are not part of the superset.
collection2.Should().BeSubsetOf(new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, });
Contain
コレクションのContain
です。Contain
には単一の要素だけではなく、別のコレクションを渡すことも可能です。
IEnumerable<int> collection = new[] { 1, 2, 5, 8 };
// 5を含むのでパス
collection.Should().Contain(5);
// 2,5を含むのでパス
collection.Should().Contain(new[] { 2, 5 });
// 失敗 Expected collection {1, 2, 5, 8} to contain {2, 5, 9}, but could not find {9}.
collection.Should().Contain(new[] { 2, 5, 9 });
Contain(条件式)
は、検証対象のコレクションに最低1つでも条件に合致する要素があるかを検証します。
IEnumerable<int> collection1 = new[] { 1, 2, 5, 8 };
// 3よりも大きい要素が存在するため、パスする
collection1.Should().Contain(x => x > 3);
// 失敗 Expected collection {1, 2, 5, 8} to have an item matching (x > 100).
collection1.Should().Contain(x => x > 100);
ContainSingle
ContainSingle
は、要素が1つであることを検証するメソッドです。
IEnumerable<int> collection1 = new[] { 4 };
IEnumerable<int> collection2 = new[] { 4, 5 };
// 要素数が 1 であることを検証
// パスする
collection1.Should().ContainSingle();
// 失敗 Expected collection2 to contain a single item, but found {4, 5}.
collection2.Should().ContainSingle();
Contain
同様に条件式を指定し、より詳細に要素を検証することも可能です。
IEnumerable<string> collection3 = new[] { "test" };
IEnumerable<string> collection4 = new[] { "test", "test" };
// 要素数が1つであることとその値についての検証
// パスする
collection3.Should().ContainSingle(x => x.Contains("test"));
// 失敗 Expected collection4 to contain a single item matching x.Contains("test"), but 2 such items were found.
collection4.Should().ContainSingle(x => x.Contains("test"));
OnlyContain
OnlyContain
は、コレクション内のすべての要素が指定された条件を満たすことを検証するメソッドです。
IEnumerable<int> collection1 = new[] { 1, 2, 5, 8 };
IEnumerable<int> collection2 = new[] { 1, 2, 5, 8, 98, 100 };
// 全要素が10より小さいため、パスする
collection1.Should().OnlyContain(x => x < 10);
// 失敗 Expected collection2 to contain only items matching (x < 10), but {98, 100} do(es) not match.
collection2.Should().OnlyContain(x => x < 10);
ContainInOrder
ContainInOrder
は、指定した要素が順序を保った状態でコレクション内に並んでいることを検証します。この際、指定した要素は連続で並んでいる必要はありません。飛び飛びでも要素の順番が合っていれば検証は成功します。
一方で、ContainInConsecutiveOrder
は、指定した要素が連続で順序通りに並んでいるかを検証します。
var collection1 = new[] { 1, 3, 5, 7, 8 };
// パスする
collection1.Should().ContainInOrder(new[] { 1, 5, 8 });
// Expected collection1 {1, 3, 5, 7, 8} to contain items {3, 1, 5} in order, but 1 (index 1) did not appear (in the right order).
collection1.Should().ContainInOrder(new[] { 3, 1, 5 });
var collection2 = new List<int> { 1, 2, 5, 8 };
// パスする
collection2.Should().ContainInConsecutiveOrder(new[] { 2, 5, 8 });
// Expected collection2 {1, 2, 5, 8} to contain items {1, 5, 8} in order, but 5 (index 1) did not appear (in the right consecutive order).
collection2.Should().ContainInConsecutiveOrder(new[] { 1, 5, 8 });
IntersectWith
IntersectWith
は、共通要素が存在するかを検証します。
var collection = new[] { 1, 2, 5, 8 };
// 共通要素である1が存在するため、パスする
collection.Should().IntersectWith(new[] { 1, 3, 4 });
// Expected collection to intersect with {0, 3, 4}, but {1, 2, 5, 8} does not contain any shared items.
collection.Should().IntersectWith(new[] { 0, 3, 4 });
Enum
Be / BeOneOf
enum MyEnum { One = 1, Two = 2, Three = 3}
var myEnum = MyEnum.One;
myEnum.Should().Be(MyEnum.One);
myEnum.Should().NotBe(MyEnum.Two);
myEnum.Should().BeOneOf(MyEnum.One, MyEnum.Two);
HaveSameNameAs / HaveSameValueAs
異なる型の名前や値を検証したい場合は、HaveSameNameAs
とHaveSameValueAs
が使えます。
enum MyEnum { One = 1, Two = 2, Three = 3}
enum SameNameEnum { One = 11 }
enum SameValueEnum { OneOne = 1 }
var myEnumOne = MyEnum.One;
// "One"という名称が同一なのでパスする
myEnumOne.Should().HaveSameNameAs(SameNameEnum.One);
// 1という値が同一なのでパスする
myEnumOne.Should().HaveSameValueAs(SameValueEnum.OneOne);
HaveValue
定義されている数値自体を検証するメソッドとして、HaveValue
があります。
MyEnum.One.Should().HaveValue(1);
MyEnum.One.Should().NotHaveValue(2);
その他
Fluent Assertions
は、失敗した時に分かりやすく失敗原因を提示してくれます。
しかし、各Assertion
のbecause
引数を指定することで、さらに失敗原因を分かりやすくすることも可能です。
// 失敗 Expected collection to be equal to {1, 2, 5, 8, 9} because 一致しない理由, but {1, 2, 5, 8} contains 1 item(s) less.
collection.Should().Equal(new List<int> { 1, 2, 5, 8, 9 }, "一致しない理由");
// 失敗 Expected collection to contain 3 item(s) because 総数不一致, but found 4: {1, 2, 5, 8}.
collection.Should().HaveCount(3, "総数不一致");
最後に
以上です。
Fluent Assertions
を使っている割には、知らないメソッドが沢山ありました。必要なタイミングで積極的に使っていきたいですね。
Discussion