xUnit Assertions
Like most testing frameworks, the xUnit framework provides a host of specialized assertions.
Boolean Assertions
For example, xUnit provides two boolean assertions:
Assert.True(bool actual)
, asserts that the value supplied to theactual
parameter istrue
.Assert.False(bool actual)
, asserts that the value supplied to theactual
parameter isfalse
.
While it may be tempting to use Assert.True()
for all tests, i.e. Assert.True(stove.BurnerOne == 0)
, it is better practice to use the specialized assertion that best matches the situation, in this case Assert.Equal<T>(T expected, T actual)
as a failing test will supply more details.
Equality Assertions
The Assert.Equal<T>(T expected, T actual)
is the workhorse of the assertion library. Notice it is a template method, so it can be used with any type that is comparable (which is pretty much everything possible in C#). It also has an override, Assert.Equal<T>(T expected, T actual, int precision)
which allows you to specify the precision for floating-point numbers. Remember that floating point error can cause two calculated values to be slightly different than one another; specifying a precision allows you to say just how close to the expected an actual value needs to be to be considered ’equal’ for the purposes of the test.
Like most assertions, it is paired with an opposite, Assert.NotEqual<T>(T expected, T actual)
, which also has an override for supplying precision.
Numeric Assertions
With numeric values, it can be handy to determine if the value falls within a range:
Assert.InRange<T>(T actual, T low, T high)
assertsactual
falls betweenlow
andhigh
(inclusive), andAssert.NotInRange<T>(T actual, T low, T high)
assertsactual
does not fall betweenlow
andhigh
(inclusive)
Reference Assertions
There are special assertions to deal with null references:
Assert.Null(object object)
asserts the suppliedobject
is null, andAssert.NotNull(object object)
asserts the suppliedobject
is not null
In addition, two objects may be considered equal, but may or may not be the same object (i.e. not referencing the same memory). This can be asserted with:
Assert.Same(object expected, object actual)
asserts theexpected
andactual
object references are to the same object, whileAssert.NotSame(object expected, object actual)
asserts theexpected
andactual
object references are not the same object
Type Assertions
At times, you may want to assure it is possible to cast an object to a specific type. This can be done with:
Assert.IsAssignableFrom<T>(object obj)
WhereT
is the type to cast into.
At other times, you may want to assert that the object is exactly the type you expect (.e. T
is not an interface or base class of obj
). That can be done with:
Assert.IsType<T>(object obj)
Collection Assertions
There are a host of assertions for working with collections:
Assert.Empty(IEnumerable collection)
asserts that the collection is empty, whileAssert.NotEmpty(IEnumerable collection)
asserts that it is not emptyAssert.Contains<T>(T expected, IEnumerable<T> collection)
asserts that theexpected
item is found in thecollection
, whileAssert.DoesNotContain<T>(T expected, IEnumerable<T> collection)
asserts theexpected
item is not found in thecollection
In addition to the simple equality check form of Assert.Contains<T>()
and Assert.DoesNotContain<T>()
, there is a version that takes a filter expression (an expression that evaluates to true
or false
indicating that an item was found) written as a lambda expression. For example, to determine if a list of Fruit
contains an Orange
we could use:
List<Fruit> fruits = new List<Fruit>() {
new Orange(),
new Apple(),
new Grape(),
new Banana() {Overripe = true}
};
Assert.Contains(fruits, item => item is Orange);
The expression item is Orange
is run on each item in fruits
until it evaluates to true
or we run out of fruit to check. We can also supply curly braces with a return statement if we need to perform more complex logic:
Assert.Contains(fruits, item => {
if(item is Banana banana) {
if(banana.Overripe) return true;
}
return false;
});
Here we only return true
for overripe bananas. Using Assert.Contains()
with a filter expression can be useful for checking that expected items are in a collection. To check that the collection also does not contain unexpected items, we can test the length of the collection against the expected number of values, i.e.:
Assert.True(fruits.Count == 4, $"Expected 4 items but found {fruits.Count}");
Here we use the Assert.True()
overload that allows a custom message when the test fails.
Finally, Assert.Collection<T>(IEnumerable<T> collection, Action<T>[] inspectors)
can apply specific inspectors against each item in a collection. Using the same fruits list as above:
Assert.Collection(fruits,
item => Assert.IsType<Orange>(item),
item => Assert.IsType<Apple>(item),
item => Assert.IsType<Grape>(item),
item => {
Assert.IsType<Banana>(item);
Assert.True(((Banana)item).Overripe);
}
);
Here we use an Action
The number of actions should correspond to the expected size of the collection, and the items supplied to the actions must be in the same order as they appear in the collection. Thus, the Assert.Collection()
is a good choice when the collection is expected to always be in the same order, while the Assert.Contains()
approach allows for variation in the ordering.
Exception Assertions
Error assertions also use ActionSystem.DivideByZeroException
with:
[Fact]
public void DivisionByZeroShouldThrowException() {
Assert.Throws(System.DivideByZeroException, () => {
var tmp = 10.0/0.0;
});
}
Note how we place the code that is expected to throw the exception inside the body of the Action? This allows the assertion to wrap it in a try/catch
internally. The exception-related assertions are:
Assert.Throws(System.Exception expectedException, Action testCode)
asserts the suppliedexpectedException
is thrown whentestCode
is executedAssert.Throws<T>(Action testCode) where T : System.Exception
the templated version of the aboveAssert.ThrowsAny<T>(Action testCode) where T: System.Exception
asserts that any exception will be thrown by thetestCode
when executed
There are also similar assertions for exceptions being thrown in asynchronous code. These operate nearly identically, except instead of supplying an Action, we supply a Task:
Assert.ThrowsAsync<T>(Task testCode) where T : System.Exception
asserts the supplied exception typeT
is thrown whentestCode
is executedAssert.ThrowsAnyAsync<T>(Task testCode) where T: System.Exception
is the asynchronous version of the previous assertion, asserts the supplied exception typeT
will be thrown some point aftertestCode
is executed.
Events Assertions
Asserting that events will be thrown also involves Action
For example, assume we have a class, Emailer
, with a method SendEmail(string address, string body)
that should have an event handler EmailSent
whose event args are EmailSentEventArgs
. We could test that this class was actually raising this event with:
[Fact]
public void EmailerShouldRaiseEmailSentWhenSendingEmails()
{
string address = "test@test.com";
string body = "this is a test";
Emailer emailer = new Emailer();
Assert.Raises<EmailSentEventArgs>(
listener => emailer += listener, // This action attaches the listener
listener => emailer -= listener, // This action detaches the listener
() => {
emailer.SendEmail(address, body);
}
)
}
The various event assertions are:
Assert.Raises<T>(Action attach, Action detach, Action testCode)
Assert.RaisesAny<T>(Action attach, Action detach, Action testCode)
There are also similar assertions for events being raised by asynchronous code. These operate nearly identically, except instead of supplying an Action, we supply a Task:
Assert.RaisesAsync<T>(Action attach, Action detach, Task testCode)
Assert.RaisesAnyAsync<T>(Action attach, Action detach, Task testCode)
For examples of these assertions, see section 2.3.10
XUnit does not directly support old-style events - those with a named event handler like CollectionChangedEventHandler
, only those that use the templated form: EventHandler<CustomEventArgs>
(with the exception of the PropertyChanged
event, discussed below). For strategies to handle the older-style events, see section 2.3.11
Property Change Assertions
Because C# has deeply integrated the idea of ‘Property Change’ notifications as part of its GUI frameworks (which we’ll cover in a later chapter), it makes sense to have a special assertion to deal with this notification. Hence, the Assert.PropertyChanged(INotifyPropertyChanged @object, string propertyName, Action testCode)
. Using it is simple - supply the object that implements the INotifyPropertyChanged
interface as the first argument, the name of the property that will be changing as the second, and the Action delegate that will trigger the change as the third.
For example, if we had a Profile
object with a StatusMessage
property that we knew should trigger a notification when it changes, we could write our test as:
[Fact]
public void ProfileShouldNotifyOfStatusMessageChanges() {
Profile testProfile = new Profile();
Assert.PropertyChanged(testProfile, "StatusMessage", () => testProfile.StatusMessage = "Hard at work");
}
There is also a similar assertion for testing if a property is changed in asynchronous code. This operates nearly identically, except instead of supplying an Action, we supply a Task:
Assert.PropertyChangedAsync(INotifyPropertyChanged @object, string propertyName, Task testCode)