sources/Google.Solutions.Testing.Apis/ExceptionAssert.cs (164 lines of code) (raw):
//
// Copyright 2019 Google LLC
//
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
//
using Google.Solutions.Common.Util;
using NUnit.Framework;
using NUnit.Framework.Constraints;
using NUnit.Framework.Internal;
using System;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq.Expressions;
using System.Reflection;
using System.Threading.Tasks;
namespace Google.Solutions.Testing.Apis
{
public static class ExceptionAssert
{
/// <summary>
/// Assert that the delegate throws an exception, possibly
/// wrapped in an AggregateException.
/// </summary>
public static async Task<TActual> ThrowsAsync<TActual>(
AsyncTestDelegate code,
string? message = null)
where TActual : Exception
{
Exception? caughtException = null;
using (new TestExecutionContext.IsolatedContext())
{
try
{
await code().ConfigureAwait(false);
}
catch (Exception e)
{
caughtException = e.Unwrap();
}
}
Assert.That(
caughtException,
new ExceptionTypeConstraint(typeof(TActual)),
message);
return (TActual)caughtException!;
}
public static TActual? ThrowsAggregateException<TActual>(TestDelegate code)
where TActual : Exception
{
return Assert.Throws<TActual>(() =>
{
try
{
code();
}
catch (AggregateException e)
{
throw e.Unwrap();
}
});
}
}
public static class PropertyAssert
{
public static void ArePropertiesEqual<T>(T expected, T actual)
{
var properties = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance);
foreach (var property in properties)
{
var expectedValue = property.GetValue(expected, null);
var actualValue = property.GetValue(actual, null);
Assert.AreEqual(expectedValue, actualValue, $"{property.Name} must match");
}
}
public static void RaisesPropertyChangedNotification(
INotifyPropertyChanged obj,
Action action,
string property)
{
var callbacks = 0;
void handler(object sender, PropertyChangedEventArgs args)
{
Assert.AreSame(obj, sender);
if (property == args.PropertyName)
{
callbacks++;
}
}
obj.PropertyChanged += handler;
action();
obj.PropertyChanged -= handler;
Assert.AreEqual(
1,
callbacks,
$"Expected PropertyChanged callback for {property}");
}
public static void RaisesPropertyChangedNotification<T, TProperty>(
T obj,
Action action,
Expression<Func<T, TProperty>> property) where T : INotifyPropertyChanged
{
Debug.Assert(property.NodeType == ExpressionType.Lambda);
if (property.Body is MemberExpression memberExpression &&
memberExpression.Member is PropertyInfo propertyInfo)
{
RaisesPropertyChangedNotification(
obj,
action,
propertyInfo.Name);
}
else
{
throw new ArgumentException("Expression does not resolve to a property");
}
}
public static void RaisesCollectionChangedNotification(
INotifyCollectionChanged obj,
Action action,
NotifyCollectionChangedAction expected)
{
var callbacks = 0;
void handler(object sender, NotifyCollectionChangedEventArgs args)
{
Assert.AreSame(obj, sender);
if (args.Action == expected)
{
callbacks++;
}
}
obj.CollectionChanged += handler;
action();
obj.CollectionChanged -= handler;
Assert.AreEqual(
1,
callbacks,
$"Expected CollectionChanged callback for {expected}");
}
}
public static class EventAssert
{
[SuppressMessage("Usage", "VSTHRD103:Call async methods when in an async method", Justification = "")]
public static async Task<TArgs> RaisesEventAsync<TArgs>(
Action<Action<TArgs>> registerEvent,
TimeSpan timeout)
where TArgs : EventArgs
{
var completionSource = new TaskCompletionSource<TArgs>();
registerEvent(args => completionSource.SetResult(args));
if (await Task
.WhenAny(completionSource.Task, Task.Delay(timeout))
.ConfigureAwait(true) == completionSource.Task)
{
return completionSource.Task.Result;
}
else
{
throw new AssertionException(
"Timeout elapsed before event");
}
}
public static Task<TArgs> RaisesEventAsync<TArgs>(
Action<Action<TArgs>> registerEvent)
where TArgs : EventArgs
{
return RaisesEventAsync<TArgs>(
registerEvent,
TimeSpan.FromSeconds(30));
}
}
}