ℹ️ Information
The code in the following article was tested on .NET and ASP.NET 6 and 7.
Fluent Assertions was created to provide a more expressive way of writing assertions in .NET unit tests. Typically, assertions in unit tests can be syntactically complex and not very expressive, which makes them hard to read and understand. Fluent Assertions aims to make assertions more human-readable by using a natural language syntax.
This library extends the traditional assertions provided by frameworks like MSTest, NUnit, or XUnit by offering a more extensive set of extension methods. Fluent Assertions supports a wide range of types like collections, strings, and objects and even allows for more advanced assertions like throwing exceptions.
Installation
You can install Fluent Assertions through the NuGet Package Manager:
Install-Package FluentAssertions
Or using the .NET CLI from a terminal window:
dotnet add package FluentAssertions
First assertion
Suppose you have an integer variable result
:
var result = 10;
In order to check the value of the integer variable result
, you can write the assertion in the following way:
result.Should().Be(expectedValue)
where expectedValue
is the value you expect result to be. In this expression, the Should()
extension method plays a key role, as it serves as the entry point for the Fluent Assertions library's functionality.
One of the primary strengths of Should()
is its contribution to readability. By using Should()
, the assertions begin to resemble natural language. Take for example result.Should().Be(10)
it reads almost like an english sentence: "Result should be 10". This natural flow makes it far simpler for developers to comprehend what exactly is being tested.
Furthermore, Should()
empowers the utilization of the fluent interface pattern. This design allows for a seamless chaining of methods, which not only keeps the code succinct, but also substantially augments the expressiveness of tests.
Additionally, the Fluent Assertions library is structured to support extensive customization and flexibility through the methods available after calling Should()
. With a rich set of options such as BeGreaterThan()
, BeOfType()
and many others, it allows for crafting assertions that can efficiently address a wide range of testing scenarios.
Another significant aspect of using Should()
is how it impacts maintenance and communication. Tests often double as documentation, providing insights into the expected behavior of code. When Should()
is employed to craft assertions, the tests closely mirror spoken language. This closeness is invaluable as it not only makes maintenance easier but also facilitates clear communication among developers. This is particularly beneficial for those who may not have an in-depth familiarity with the codebase.
Nullable types
For asserting whether a variable has (not) value or is (not) null
, the following methods can be used:
int? result = null;
result.Should().BeNull();
result.Should().HaveValue();
result.Should().NotBeNull();
result.Should().NotHaveValue();
Furthermore, it is possible to verify a nullable type via a predicate:
int? result = 10;
result.Should().Match(x => !x.HasValue);
Booleans
For asserting whether a variable is true
or false
, you can use:
var result = true;
var otherBoolean = true;
result.Should().BeTrue();
result.Should().BeFalse();
result.Should().Be(otherBoolean);
result.Should().NotBe(otherBoolean);
Another interesting method for booleans is Imply()
, which exploits the concept of boolean implication.
var result = false;
var otherBoolean = true;
result.Should().Imply(otherBoolean);
Boolean implication A implies B
simply means "if A is true, then B is also true". This implies that if A is not true, then B can be anything. The symbol used to denote implies
is A => B
. Thus:
A | B | A => B |
---|---|---|
T | T | T |
T | F | F |
F | T | T |
F | F | T |
A => B
is an abbreviation for (not A) or B
, i.e., "either A is false, or B must be true".
Strings
For asserting whether a string is null
, empty, or contains (not) whitespace only, you have several methods at your disposal:
var result = "This is an example";
result.Should().BeNull();
result.Should().BeEmpty();
result.Should().HaveLength(0);
result.Should().BeNullOrWhiteSpace();
result.Should().NotBeNull();
result.Should().NotBeEmpty();
result.Should().NotBeNullOrWhiteSpace();
To ensure the characters in a string are all (not) upper or lower cased, you can use the following assertions:
var result = "This is an example";
result.Should().BeUpperCased();
result.Should().BeLowerCased();
result.Should().NotBeUpperCased();
result.Should().NotBeLowerCased();
⚠️ Warning
Numbers and special characters do not have casing, soBeUpperCased
andBeLowerCased
will always fail on a string that contains anything but alphabetic characters.
Of course, there are also other methods for string assertions:
var result = "This is an example";
result.Should().Be("This is an example");
result.Should().BeEquivalentTo("THIS IS AN EXAMPLE");
result.Should().NotBe("This is another example");
result.Should().NotBeEquivalentTo("THIS IS ANOTHER EXAMPLE");
result.Should().BeOneOf("That is an example", "This is an example");
result.Should().Contain("is an");
result.Should().Contain("is an", AtMost.Times(5));
result.Should().ContainAll("should", "contain", "all", "of", "these");
result.Should().ContainAny("any", "of", "these", "will", "do");
result.Should().ContainEquivalentOf("THIS IS AN EXAMPLE");
result.Should().ContainEquivalentOf("THIS IS AN EXAMPLE", Exactly.Thrice());
result.Should().NotContain("is an");
result.Should().NotContainAll("can", "contain", "some", "but", "not", "all");
result.Should().NotContainAny("cannot", "contain", "any", "of", "these");
result.Should().NotContainEquivalentOf("THIS IS ANOTHER EXAMPLE");
result.Should().StartWith("This");
result.Should().StartWithEquivalentOf("THIS");
result.Should().NotStartWith("That");
result.Should().NotStartWithEquivalentOf("THAT");
result.Should().EndWith("an example");
result.Should().EndWithEquivalentOf("AN EXAMPLE");
result.Should().NotEndWith("an other example");
result.Should().NotEndWithEquivalentOf("AN OTHER EXAMPLE");
📝 Note
All methods ending withEquivalentOf
are case insensitive.
If you prefer a more fluent syntax than Exactly.Once()
, AtLeast.Twice()
, AtMost.Times(5)
and so on, you can proceed as follows:
var result = "This is an example";
result.Should().Contain("is an", 1.TimesExactly()); // equivalent to Exactly.Once()
result.Should().Contain("is an", 2.TimesOrMore()); // equivalent to AtLeast.Twice()
result.Should().Contain("is an", 5.TimesOrLess()); // equivalent to AtMost.Times(5)
In addition, the Match
, NotMatch
, MatchEquivalentOf
and NotMatchEquivalentOf
methods support the following wildcards in the pattern:
Wildcard specifier | Matches |
---|---|
* (asterisk) | Zero or more characters in that position. |
? (question mark) | Exactly one character in that position. |
⚠️ Warning
The wildcard pattern does not support regular expressions.
For instance, if you would like to assert that some email address is correct, use this:
var result = "john.doe@example.com";
result.Should().Match("*@*.com");
Numerics
For asserting a numeric variable, you have several methods at your disposal:
var result = 10;
result.Should().Be(10);
result.Should().NotBe(5);
result.Should().BeGreaterThan(5);
result.Should().BeGreaterThanOrEqualTo(5);
result.Should().BeLessThan(15);
result.Should().BeLessThanOrEqualTo(15);
result.Should().BePositive();
result.Should().BeNegative();
result.Should().BeInRange(5, 15);
result.Should().NotBeInRange(11, 20);
result.Should().Match(x => x % 2 == 0);
result.Should().BeOneOf(5, 10, 15);
The Should().Be()
and Should().NotBe()
methods are not available for floats and doubles, this is because floating-point variables are inherently imprecise and should never be compared for equality. Instead, you can use BeInRange()
or the following method:
var result = 3.1415927F;
result.Should().BeApproximately(3.14F, 0.01F);
This will verify that the value of the float is between 3.139 and 3.141.
Conversely, to assert that the value differs by an amount, you can do this:
var result = 3.5F;
result.Should().NotBeApproximately(2.5F, 0.5F);
This will verify that the value of the float is not between 2.0 and 3.0.
Dates and times
For asserting a DateTime
, several methods are provided:
var result = new DateTime(2023, 07, 07, 12, 15, 00, DateTimeKind.Utc);
result.Should().Be(7.July(2023).At(12, 15));
result.Should().BeAfter(1.June(2023));
result.Should().BeBefore(1.August(2023));
result.Should().BeOnOrAfter(1.June(2023));
result.Should().BeOnOrBefore(1.August(2023));
result.Should().BeSameDateAs(7.July(2023).At(12, 16));
result.Should().NotBe(1.July(2023).At(12, 15));
result.Should().NotBeAfter(2.August(2023));
result.Should().NotBeBefore(1.June(2023));
result.Should().NotBeOnOrAfter(2.August(2023));
result.Should().NotBeOnOrBefore(1.June(2023));
result.Should().NotBeSameDateAs(6.July(2023));
result.Should().BeIn(DateTimeKind.Utc);
result.Should().BeOneOf(
7.July(2023).At(12, 15),
7.July(2023).At(13, 15),
7.July(2023).At(14, 15)
);
If you only care about specific parts of a date or time, use the following assertion methods instead:
var result = new DateTime(2023, 07, 07, 12, 15, 00, DateTimeKind.Utc);
result.Should().HaveDay(7);
result.Should().HaveMonth(7);
result.Should().HaveYear(2023);
result.Should().HaveHour(12);
result.Should().HaveMinute(15);
result.Should().HaveSecond(0);
result.Should().NotHaveDay(1);
result.Should().NotHaveMonth(6);
result.Should().NotHaveYear(2022);
result.Should().NotHaveHour(11);
result.Should().NotHaveMinute(16);
result.Should().NotHaveSecond(1);
To assert that a date/time is (not) within a specified time span from another date/time value you can use this method:
result.Should().BeCloseTo(7.July(2023).At(12, 15), 2.Seconds());
result.Should().NotBeCloseTo(8.July(2023), 1.Hours());
This can be particularly useful if your database truncates date/time values.
Collections
For asserting a collection, both have two possibilities:
1️⃣ Perform assertions on the entire collection
IEnumerable<int> collection = new[] { 1, 2, 3, 4 };
collection.Should().BeEmpty();
collection.Should().BeNullOrEmpty();
collection.Should().NotBeNullOrEmpty();
collection.Should().Equal(new List<int> { 1, 2, 3, 4 });
collection.Should().Equal(1, 2, 3, 4);
collection.Should().BeEquivalentTo(new[] { 1, 2, 3, 4 });
collection.Should().NotEqual(new List<int> { 5, 6, 7, 8 });
collection.Should().NotBeEquivalentTo(new[] { 5, 6, 7, 8 });
collection.Should().HaveCount(4);
collection.Should().HaveCountGreaterThan(3);
collection.Should().HaveCountGreaterThanOrEqualTo(4);
collection.Should().HaveCountLessThan(5);
collection.Should().HaveCountLessThanOrEqualTo(4);
collection.Should().HaveSameCount(new[] { 6, 2, 0, 5 });
collection.Should().NotHaveCount(3);
collection.Should().NotHaveSameCount(new[] { 6, 2, 0 });
collection.Should().StartWith(1);
collection.Should().StartWith(new[] { 1, 2 });
collection.Should().EndWith(4);
collection.Should().EndWith(new[] { 3, 4 });
collection.Should().BeSubsetOf(new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, });
collection.Should().ContainSingle();
collection.Should().ContainSingle(x => x > 3);
collection.Should().Contain(x => x > 3);
collection.Should().Contain(collection, "", 5, 6); // It should contain the original items, plus 5 and 6.
collection.Should().NotContain(10);
collection.Should().NotContain(new[] { 10, 11 });
collection.Should().NotContain(x => x > 10);
collection.Should().OnlyContain(x => x < 10);
collection.Should().ContainItemsAssignableTo<int>();
collection.Should().ContainInOrder(new[] { 1, 5, 8 });
collection.Should().NotContainInOrder(new[] { 5, 1, 2 });
collection.Should().ContainInConsecutiveOrder(new[] { 2, 5, 8 });
collection.Should().NotContainInConsecutiveOrder(new[] { 1, 5, 8 });
collection.Should().NotContainNulls();
IEnumerable<int> otherCollection = new[] { 1, 2, 3, 4, 1 };
IEnumerable<int> anotherCollection = new[] { 10, 20, 30, 40, 10 };
collection.Should().IntersectWith(otherCollection);
collection.Should().NotIntersectWith(anotherCollection);
const int element = 2;
const int successor = 3;
const int predecessor = 1;
collection.Should().HaveElementPreceding(successor, element);
collection.Should().HaveElementSucceeding(predecessor, element);
To assert that a set contains elements in a certain order, it is sufficient to use one of the following methods:
IEnumerable<int> collection = new[] { 1, 2, 3, 4 };
collection.Should().BeInAscendingOrder();
collection.Should().BeInDescendingOrder();
collection.Should().NotBeInAscendingOrder();
collection.Should().NotBeInDescendingOrder();
collection.Should().BeInAscendingOrder(x => x.SomeProperty);
collection.Should().BeInDescendingOrder(x => x.SomeProperty);
collection.Should().NotBeInAscendingOrder(x => x.SomeProperty);
collection.Should().NotBeInDescendingOrder(x => x.SomeProperty);
As mentioned at the beginning of the article, multiple assertions can be concatenated with each other using the And
property:
IEnumerable<int> collection = new[] { 1, 2, 3, 4 };
collection.Should().NotBeEmpty()
.And.HaveCount(4)
.And.ContainInOrder(new[] { 2, 3 })
.And.ContainItemsAssignableTo<int>();
2️⃣ Perform individual assertions on all elements of a collection
var collection = new[]
{
new { Id = 1, Name = "John", Attributes = new string[] { } },
new { Id = 2, Name = "Jane", Attributes = new string[] { "attr" } }
};
collection.Should().SatisfyRespectively(
first =>
{
first.Id.Should().Be(1);
first.Name.Should().StartWith("J");
first.Attributes.Should().NotBeNull();
},
second =>
{
second.Id.Should().Be(2);
second.Name.Should().EndWith("e");
second.Attributes.Should().NotBeEmpty();
});
If you need to perform the same assertion on all elements of a collection:
var collection = new[]
{
new { Id = 1, Name = "John", Attributes = new string[] { } },
new { Id = 2, Name = "Jane", Attributes = new string[] { "attr" } }
};
collection.Should().AllSatisfy(x =>
{
x.Id.Should().BePositive();
x.Name.Should().StartWith("J");
x.Attributes.Should().NotBeNull();
});
If you need to perform individual assertions on all elements of a collection without setting expectation about the order of elements:
var collection = new[]
{
new { Id = 1, Name = "John", Attributes = new string[] { } },
new { Id = 2, Name = "Jane", Attributes = new string[] { "attr" } }
};
collection.Should().Satisfy(
e => e.Id == 2 && e.Name == "Jane" && e.Attributes == null,
e => e.Id == 1 && e.Name == "John" && e.Attributes != null && e.Attributes.Length > 0);
Exceptions
Imagine you have the SampleClass
class:
public class SampleClass
{
public void SampleMethod(string? value)
{
ArgumentNullException.ThrowIfNull(value);
}
}
With Fluent Assertions, you can check whether a given method throws a specific exception. In this case, using xUnit, we check that SampleMethod
throws the ArgumentNullException
exception when the argument is null
:
public class SampleClassTests
{
[Fact]
public void SampleMethodTest_WithoutValue_ThrowsException()
{
// Arrange
var subject = new SampleClass();
// Act
Action act = () => subject.SampleMethod(null);
// Assert
act.Should().Throw<ArgumentNullException>()
.WithParameterName("value");
}
}
You can also test the case when SampleMethod
throws no exception, that is, when the argument is other than null
:
public class SampleClassTests
{
[Fact]
public void SampleMethodTest_WithValue_NoThrows()
{
// Arrange
var subject = new SampleClass();
// Act
Action act = () => subject.SampleMethod("test");
// Assert
act.Should().NotThrow();
}
}
Finally, you can also check whether or not a method executed asynchronously throws an exception:
public class SampleClass
{
public Task<string> SampleMethodAsync(string? value)
{
return string.IsNullOrEmpty(value)
? Task.FromException<string>(new ArgumentException())
: Task.FromResult(value);
}
}
public class SampleClassTests
{
[Fact]
public async Task SampleMethodAsync_WithoutValue_ThrowsException()
{
// Arrange
var sampleClass = new SampleClass();
// Act
Func<Task<string>> act = async () => await sampleClass.SampleMethodAsync(null);
// Assert
await act.Should().ThrowAsync<ArgumentException>();
}
[Fact]
public async Task SampleMethodAsync_WithValue_NoThrows()
{
// Arrange
var sampleClass = new SampleClass();
// Act
Func<Task<string>> act = async () => await sampleClass.SampleMethodAsync("test");
// Assert
await act.Should().NotThrowAsync();
}
}
Object graph comparison
Consider a Person
record with an associated Address
and one or more associated EmailAddress
:
public record Person(string FirstName, string LastName, Address Address, List<EmailAddress> EmailAddresses);
public record Address(string Street, string State, string City, string ZipCode);
public record EmailAddress(string Email, EmailType Type);
public enum EmailType
{
Private,
Work
}
And to have an equivalent PersonDto
(also called DTO):
public record PersonDto(string FirstName, string LastName, Address AddressDto, List<EmailAddressDto> EmailAddressDtos);
public record AddressDto(string Street, string State, string City, string ZipCode);
public record EmailAddressDto(string Email, EmailType Type);
You might want to verify that all exposed members of all objects in the PersonDto
object graph match the equally named members of the Person
object graph.
You may assert the structural equality of two object graphs with the BeEquivalentTo()
method:
personDto.Should().BeEquivalentTo(person);
Also, you can verify the inequality of two objects with NotBeEquivalentTo()
method:
personDto.Should().NotBeEquivalentTo(person);
Let us see below the most common options that can be defined for both methods:
- Recursion
By default, the comparison is recursive and recurs up to 10 levels deep. Despite this, you can force recursion as deep as possible with the AllowingInfiniteRecursion
option:
personDto.Should().BeEquivalentTo(person, options => options.AllowingInfiniteRecursion());
or if you want to disable recursion just use the ExcludingNestedObjects
option:
personDto.Should().BeEquivalentTo(person, options => options.ExcludingNestedObjects());
- Matching members
All public members of the Person
object must be available in the PersonDto
with the same name. If any member is missing, an exception will be thrown. However, you can only include members that both graph objects have:
personDto.Should().BeEquivalentTo(person, options => options.ExcludingMissingMembers());
- Selecting members
If you want, you can exclude some members using the Excluding
option:
personDto.Should().BeEquivalentTo(person, options => options.Excluding(p => p.FirstName));
The Excluding
option also accepts a lambda expression, which provides more flexibility in deciding which member to exclude:
personDto.Should().BeEquivalentTo(person, options => options.Excluding(p => p.FirstName == "John"));
You can also decide to exclude a member of a particular nested object based on its index:
personDto.Should().BeEquivalentTo(person, options => options.Excluding(p => p.EmailAddresses[1]));
You can use For
and Exclude
if you want to exclude a member on each nested object regardless of its index:
personDto.Should().BeEquivalentTo(person, options => options.For(p => p.EmailAddresses).Exclude(ea => ea.Type));
📝 Note
Excluding
andExcludingMissingMembers
can be combined.
In a mirrored manner, specific properties or lambdas can be included using the Including
option:
personDto.Should().BeEquivalentTo(person, options => options.Including(p => p.FirstName));
personDto.Should().BeEquivalentTo(person, options => options.Including(p => p.FirstName == "John"));
- Including properties and/or fields
You can also configure whether to include or exclude the inclusion of all public properties and fields. This behavior can be modified:
// Include properties (which is the default)
personDto.Should().BeEquivalentTo(person, options => options.IncludingProperties());
// Include fields
personDto.Should().BeEquivalentTo(person, options => options.IncludingFields());
// Include internal properties as well
personDto.Should().BeEquivalentTo(person, options => options.IncludingInternalProperties());
// And the internal fields
personDto.Should().BeEquivalentTo(person, options => options.IncludingInternalFields());
// Exclude Fields
personDto.Should().BeEquivalentTo(person, options => options.ExcludingFields());
// Exclude Properties
personDto.Should().BeEquivalentTo(person, options => options.ExcludingProperties());
- Comparing members with different names
Imagine you want to compare a Person
and a PersonDto
using BeEquivalentTo
, but the first type has a FirstName
property and the second has a PersonFirstName
property. You can map them using the following option:
// Using names with the expectation member name first. Then the subject's member name.
personDto.Should().BeEquivalentTo(person, options => options.WithMapping("FirstName", "PersonFirstName"));
// Using expressions, but again, with expectation first, subject last.
personDto.Should().BeEquivalentTo(person, options => options.WithMapping<PersonDto>(p => p.FirstName, s => s.PersonFirstName));
- Enums
By default, BeEquivalentTo()
compares Enum
members by the underlying numeric value of the Enum
. However, you can compare an Enum
by name only by using the ComparingEnumsByName
option:
personDto.Should().BeEquivalentTo(person, options => options.ComparingEnumsByName());
Custom assertions
Creating a custom assertion allows you to encapsulate a more complex assertion logic that can be reused across multiple tests. This can be particularly useful when working with complex or domain-specific assertion logic that is not covered by the predefined assertion methods.
To create a custom assertion, you need to create an extension method for the type of object on which you want to assert. This extension method should contain the assertion logic and can use the existing assertion methods provided by Fluent Assertions. Finally, you need to apply the [CustomAssertion]
attribute to your extension method to signal that it is a custom assertion method.
Here is an example:
public static class PersonAssertions
{
[CustomAssertion]
public static void NotBeDoe(this Person? person, string because = "", params object[] becauseArgs)
{
Execute.Assertion.ForCondition(person is not null)
.BecauseOf(because, becauseArgs)
.FailWith("Expected person not to be null.");
Execute.Assertion.ForCondition(!string.IsNullOrEmpty(person!.FirstName))
.BecauseOf(because, becauseArgs)
.FailWith("Expected person's first name not to be null or empty.");
Execute.Assertion.ForCondition(string.Equals(person.LastName, "doe", StringComparison.OrdinalIgnoreCase))
.BecauseOf(because, becauseArgs)
.FailWith("Expected person must not have last name Doe.");
}
}
This code first verifies that the Person
object is not null
. If it is, it will throw an exception with the message "Expected person not to be null."
.
Next, it checks whether the FirstName
of the Person
object is not null
or empty. If it is, it will trigger an exception with the message "Expected person's first name not to be null or empty."
.
Lastly, it confirms that the LastName
of the Person
object is not equal to "doe"
, ignoring case differences. If it is, an exception will be thrown with the message "Expected person must not have last name Doe."
.
The because
and becauseArgs
parameters can be used to specify a custom reason for the assertion to hold true.
This is how to use the custom method:
public class PersonTests
{
[Fact]
public void Person_WithLastNameDoe_Fails()
{
var person = new Person("John", "Doe");
person.NotBeDoe(); // Fails
}
}
Assertion scopes
AssertionScope
is a powerful function that allows multiple assertions to be grouped into a single scope. When you use it, all assertions within the scope are executed and any failures are reported together at the end. This is in contrast to the default behavior, in which test execution stops at the first failed assertion.
The advantage of using AssertionScope
is that you can see all failed assertions in a single test execution, instead of correcting one, rerunning the tests, and then finding the next one. This can be especially useful when writing tests with multiple assertions and you want to get an overview of all the problems at once.
Here is how you can use AssertionScope
:
public record Person(string FirstName, string LastName);
public class PersonTests
{
[Fact]
public void AssertionScopeTest()
{
var person = new Person("John", "Doe");
using (new AssertionScope())
{
person.FirstName.Should().Be("Jane");
person.LastName.Should().Be("Smith");
}
}
}
In this example, even though the first assertion fails, the second assertion is also executed, and both failures are reported together.
Execution time
Fluent Assertions also provides a method to assert that the execution time of particular method or action does not exceed a predefined value.
To check the execution time of a method, you can write:
public class SampleClass
{
public string ExpensiveMethod()
{
var temp = string.Empty;
for (var i = 0; i < short.MaxValue; i++)
{
temp += i;
}
return temp;
}
}
public class SampleTests
{
[Fact]
public void SampleMethodTests()
{
var sampleClass = new SampleClass();
sampleClass.ExecutionTimeOf(s => s.ExpensiveMethod()).Should().BeLessOrEqualTo(1.Seconds());
}
}
Alternatively, to check the execution time of an arbitrary action, you can write in this other way:
Action someAction = () => Thread.Sleep(100);
someAction.ExecutionTime().Should().BeLessThanOrEqualTo(200.Milliseconds());
ExecutionTime
supports the same assertions as TimeSpan
, namely:
someAction.ExecutionTime().Should().BeLessThan(200.Milliseconds());
someAction.ExecutionTime().Should().BeGreaterThan(100.Milliseconds());
someAction.ExecutionTime().Should().BeGreaterThanOrEqualTo(100.Milliseconds());
someAction.ExecutionTime().Should().BeCloseTo(150.Milliseconds(), 50.Milliseconds());
If you are dealing with a Task
, you can also assert that it completed within a specified period of time or not completed:
Func<Task> someAsyncWork = () => SomethingReturningATask();
await someAsyncWork.Should().CompleteWithinAsync(100.Milliseconds());
await someAsyncWork.Should().NotCompleteWithinAsync(100.Milliseconds());
await someAsyncWork.Should().ThrowWithinAsync<InvalidOperationException>(100.Milliseconds());
Conclusion
In this article, we have explored just some assertion methods of primitive types, collections, and exceptions, and we have also seen some advanced features such as creating custom assertions or checking the execution time of a single method.
All this leads us to the conclusion that Fluent Assertions is a valuable tool for any developer writing tests in .NET. Because of its emphasis on natural language syntax, it promotes greater readability, which is critical for understanding and maintaining efficient code bases. It also simplifies communication within teams, making it easier to convey the intention behind the tests.
Top comments (6)
Thanks for sharing - there's some great info in this article.
BeApproximately
seems like a great shortcut for asserting against floating point values, where it'd otherwise be advisable to check that a value is both above a value and below another one too.Thanks for reading @ant_f_dev
BeApproximately
is definitely an interesting feature, and for floating-point types it is ideal. However, my favorite method isBeEquivalentTo
which allows you to see if two objects are equal and the ability to customize it with different options makes it very useful.Is very good ,Thanks 💡🏆
Thank you 😊
great article - it took good amount of time finishing up the read (I guess breaking it up in multi part would be more better for readers)
Thank you for your support 🙏
Initially I had thought of splitting it up, given the length of the article due mainly to the many methods of Fluent Assertions extensions (consider that not all of them are given here otherwise the article would not end 😅).
Subsequently, however, I decided to put it all together so as to have a more "smooth" reading experience.
In any case, I appreciate your suggestion is I will try to adapt for future articles 😉