File: EventAsserts.cs
Web Access
Project: src\src\Microsoft.DotNet.XUnitAssert\src\Microsoft.DotNet.XUnitAssert.csproj (xunit.assert)
#if XUNIT_NULLABLE
#nullable enable
#else
// In case this is source-imported with global nullable enabled but no XUNIT_NULLABLE
#pragma warning disable CS8600
#pragma warning disable CS8603
#pragma warning disable CS8622
#pragma warning disable CS8625
#endif

using System;
using System.Threading.Tasks;
using Xunit.Sdk;

namespace Xunit
{
#if XUNIT_VISIBILITY_INTERNAL
	internal
#else
	public
#endif
	partial class Assert
	{
		/// <summary>
		/// Verifies that an event is raised.
		/// </summary>
		/// <param name="attach">Code to attach the event handler</param>
		/// <param name="detach">Code to detach the event handler</param>
		/// <param name="testCode">A delegate to the code to be tested</param>
		/// <exception cref="RaisesException">Thrown when the expected event was not raised.</exception>
		public static void Raises(
			Action<Action> attach,
			Action<Action> detach,
			Action testCode)
		{
			if (!RaisesInternal(attach, detach, testCode))
				throw RaisesException.ForNoEvent();
		}

		/// <summary>
		/// Verifies that an event with the exact event args is raised.
		/// </summary>
		/// <typeparam name="T">The type of the event arguments to expect</typeparam>
		/// <param name="attach">Code to attach the event handler</param>
		/// <param name="detach">Code to detach the event handler</param>
		/// <param name="testCode">A delegate to the code to be tested</param>
		/// <returns>The event sender and arguments wrapped in an object</returns>
		/// <exception cref="RaisesException">Thrown when the expected event was not raised.</exception>
		public static RaisedEvent<T> Raises<T>(
			Action<Action<T>> attach,
			Action<Action<T>> detach,
			Action testCode)
		{
			var raisedEvent = RaisesInternal(attach, detach, testCode);

			if (raisedEvent == null)
				throw RaisesException.ForNoEvent(typeof(T));

			if (raisedEvent.Arguments != null && !raisedEvent.Arguments.GetType().Equals(typeof(T)))
				throw RaisesException.ForIncorrectType(typeof(T), raisedEvent.Arguments.GetType());

			return raisedEvent;
		}

		/// <summary>
		/// Verifies that an event with the exact event args is raised.
		/// </summary>
		/// <typeparam name="T">The type of the event arguments to expect</typeparam>
		/// <param name="attach">Code to attach the event handler</param>
		/// <param name="detach">Code to detach the event handler</param>
		/// <param name="testCode">A delegate to the code to be tested</param>
		/// <returns>The event sender and arguments wrapped in an object</returns>
		/// <exception cref="RaisesException">Thrown when the expected event was not raised.</exception>
		public static RaisedEvent<T> Raises<T>(
			Action<EventHandler<T>> attach,
			Action<EventHandler<T>> detach,
			Action testCode)
		{
			var raisedEvent = RaisesInternal(attach, detach, testCode);

			if (raisedEvent == null)
				throw RaisesException.ForNoEvent(typeof(T));

			if (raisedEvent.Arguments != null && !raisedEvent.Arguments.GetType().Equals(typeof(T)))
				throw RaisesException.ForIncorrectType(typeof(T), raisedEvent.Arguments.GetType());

			return raisedEvent;
		}

		/// <summary>
		/// Verifies that an event with the exact event args is raised.
		/// </summary>
		/// <typeparam name="T">The type of the event arguments to expect</typeparam>
		/// <param name="handler">Code returning the raised event</param>
		/// <param name="attach">Code to attach the event handler</param>
		/// <param name="detach">Code to detach the event handler</param>
		/// <param name="testCode">A delegate to the code to be tested</param>
		/// <returns>The event sender and arguments wrapped in an object</returns>
		/// <exception cref="RaisesException">Thrown when the expected event was not raised.</exception>
		public static RaisedEvent<T> Raises<T>(
#if XUNIT_NULLABLE
			Func<RaisedEvent<T>?> handler,
#else
			Func<RaisedEvent<T>> handler,
#endif
			Action attach,
			Action detach,
			Action testCode)
		{
			var raisedEvent = RaisesInternal(handler, attach, detach, testCode);

			if (raisedEvent == null)
				throw RaisesException.ForNoEvent(typeof(T));

			if (raisedEvent.Arguments != null && !raisedEvent.Arguments.GetType().Equals(typeof(T)))
				throw RaisesException.ForIncorrectType(typeof(T), raisedEvent.Arguments.GetType());

			return raisedEvent;
		}

		/// <summary>
		/// Verifies that an event is raised.
		/// </summary>
		/// <param name="attach">Code to attach the event handler</param>
		/// <param name="detach">Code to detach the event handler</param>
		/// <param name="testCode">A delegate to the code to be tested</param>
		/// <returns>The event sender and arguments wrapped in an object</returns>
		/// <exception cref="RaisesException">Thrown when the expected event was not raised.</exception>
		public static RaisedEvent<EventArgs> RaisesAny(
			Action<EventHandler> attach,
			Action<EventHandler> detach,
			Action testCode)
		{
			var raisedEvent = RaisesInternal(attach, detach, testCode);

			if (raisedEvent == null)
				throw RaisesAnyException.ForNoEvent(typeof(EventArgs));

			return raisedEvent;
		}

		/// <summary>
		/// Verifies that an event with the exact or a derived event args is raised.
		/// </summary>
		/// <typeparam name="T">The type of the event arguments to expect</typeparam>
		/// <param name="attach">Code to attach the event handler</param>
		/// <param name="detach">Code to detach the event handler</param>
		/// <param name="testCode">A delegate to the code to be tested</param>
		/// <returns>The event sender and arguments wrapped in an object</returns>
		/// <exception cref="RaisesException">Thrown when the expected event was not raised.</exception>
		public static RaisedEvent<T> RaisesAny<T>(
			Action<Action<T>> attach,
			Action<Action<T>> detach,
			Action testCode)
		{
			var raisedEvent = RaisesInternal(attach, detach, testCode);

			if (raisedEvent == null)
				throw RaisesAnyException.ForNoEvent(typeof(T));

			return raisedEvent;
		}

		/// <summary>
		/// Verifies that an event with the exact or a derived event args is raised.
		/// </summary>
		/// <typeparam name="T">The type of the event arguments to expect</typeparam>
		/// <param name="attach">Code to attach the event handler</param>
		/// <param name="detach">Code to detach the event handler</param>
		/// <param name="testCode">A delegate to the code to be tested</param>
		/// <returns>The event sender and arguments wrapped in an object</returns>
		/// <exception cref="RaisesException">Thrown when the expected event was not raised.</exception>
		public static RaisedEvent<T> RaisesAny<T>(
			Action<EventHandler<T>> attach,
			Action<EventHandler<T>> detach,
			Action testCode)
		{
			var raisedEvent = RaisesInternal(attach, detach, testCode);

			if (raisedEvent == null)
				throw RaisesAnyException.ForNoEvent(typeof(T));

			return raisedEvent;
		}

		/// <summary>
		/// Verifies that an event is raised.
		/// </summary>
		/// <param name="attach">Code to attach the event handler</param>
		/// <param name="detach">Code to detach the event handler</param>
		/// <param name="testCode">A delegate to the code to be tested</param>
		/// <returns>The event sender and arguments wrapped in an object</returns>
		/// <exception cref="RaisesException">Thrown when the expected event was not raised.</exception>
		public static async Task<RaisedEvent<EventArgs>> RaisesAnyAsync(
			Action<EventHandler> attach,
			Action<EventHandler> detach,
			Func<Task> testCode)
		{
			var raisedEvent = await RaisesAsyncInternal(attach, detach, testCode);

			if (raisedEvent == null)
				throw RaisesAnyException.ForNoEvent(typeof(EventArgs));

			return raisedEvent;
		}

		/// <summary>
		/// Verifies that an event with the exact or a derived event args is raised.
		/// </summary>
		/// <typeparam name="T">The type of the event arguments to expect</typeparam>
		/// <param name="attach">Code to attach the event handler</param>
		/// <param name="detach">Code to detach the event handler</param>
		/// <param name="testCode">A delegate to the code to be tested</param>
		/// <returns>The event sender and arguments wrapped in an object</returns>
		/// <exception cref="RaisesException">Thrown when the expected event was not raised.</exception>
		public static async Task<RaisedEvent<T>> RaisesAnyAsync<T>(
			Action<Action<T>> attach,
			Action<Action<T>> detach,
			Func<Task> testCode)
		{
			var raisedEvent = await RaisesAsyncInternal(attach, detach, testCode);

			if (raisedEvent == null)
				throw RaisesAnyException.ForNoEvent(typeof(T));

			return raisedEvent;
		}

		/// <summary>
		/// Verifies that an event with the exact or a derived event args is raised.
		/// </summary>
		/// <typeparam name="T">The type of the event arguments to expect</typeparam>
		/// <param name="attach">Code to attach the event handler</param>
		/// <param name="detach">Code to detach the event handler</param>
		/// <param name="testCode">A delegate to the code to be tested</param>
		/// <returns>The event sender and arguments wrapped in an object</returns>
		/// <exception cref="RaisesException">Thrown when the expected event was not raised.</exception>
		public static async Task<RaisedEvent<T>> RaisesAnyAsync<T>(
			Action<EventHandler<T>> attach,
			Action<EventHandler<T>> detach,
			Func<Task> testCode)
		{
			var raisedEvent = await RaisesAsyncInternal(attach, detach, testCode);

			if (raisedEvent == null)
				throw RaisesAnyException.ForNoEvent(typeof(T));

			return raisedEvent;
		}

		/// <summary>
		/// Verifies that an event is raised.
		/// </summary>
		/// <param name="attach">Code to attach the event handler</param>
		/// <param name="detach">Code to detach the event handler</param>
		/// <param name="testCode">A delegate to the code to be tested</param>
		/// <returns>The event sender and arguments wrapped in an object</returns>
		/// <exception cref="RaisesException">Thrown when the expected event was not raised.</exception>
		public static async Task RaisesAsync(
			Action<Action> attach,
			Action<Action> detach,
			Func<Task> testCode)
		{
			if (!await RaisesAsyncInternal(attach, detach, testCode))
				throw RaisesException.ForNoEvent();
		}

		/// <summary>
		/// Verifies that an event with the exact event args (and not a derived type) is raised.
		/// </summary>
		/// <typeparam name="T">The type of the event arguments to expect</typeparam>
		/// <param name="attach">Code to attach the event handler</param>
		/// <param name="detach">Code to detach the event handler</param>
		/// <param name="testCode">A delegate to the code to be tested</param>
		/// <returns>The event sender and arguments wrapped in an object</returns>
		/// <exception cref="RaisesException">Thrown when the expected event was not raised.</exception>
		public static async Task<RaisedEvent<T>> RaisesAsync<T>(
			Action<Action<T>> attach,
			Action<Action<T>> detach,
			Func<Task> testCode)
		{
			var raisedEvent = await RaisesAsyncInternal(attach, detach, testCode);

			if (raisedEvent == null)
				throw RaisesException.ForNoEvent(typeof(T));

			if (raisedEvent.Arguments != null && !raisedEvent.Arguments.GetType().Equals(typeof(T)))
				throw RaisesException.ForIncorrectType(typeof(T), raisedEvent.Arguments.GetType());

			return raisedEvent;
		}

		/// <summary>
		/// Verifies that an event with the exact event args (and not a derived type) is raised.
		/// </summary>
		/// <typeparam name="T">The type of the event arguments to expect</typeparam>
		/// <param name="attach">Code to attach the event handler</param>
		/// <param name="detach">Code to detach the event handler</param>
		/// <param name="testCode">A delegate to the code to be tested</param>
		/// <returns>The event sender and arguments wrapped in an object</returns>
		/// <exception cref="RaisesException">Thrown when the expected event was not raised.</exception>
		public static async Task<RaisedEvent<T>> RaisesAsync<T>(
			Action<EventHandler<T>> attach,
			Action<EventHandler<T>> detach,
			Func<Task> testCode)
		{
			var raisedEvent = await RaisesAsyncInternal(attach, detach, testCode);

			if (raisedEvent == null)
				throw RaisesException.ForNoEvent(typeof(T));

			if (raisedEvent.Arguments != null && !raisedEvent.Arguments.GetType().Equals(typeof(T)))
				throw RaisesException.ForIncorrectType(typeof(T), raisedEvent.Arguments.GetType());

			return raisedEvent;
		}

		// Helpers

		static bool RaisesInternal(
			Action<Action> attach,
			Action<Action> detach,
			Action testCode)
		{
			GuardArgumentNotNull(nameof(attach), attach);
			GuardArgumentNotNull(nameof(detach), detach);
			GuardArgumentNotNull(nameof(testCode), testCode);

			var result = false;
			Action handler = () => result = true;

			attach(handler);
			testCode();
			detach(handler);
			return result;
		}

#if XUNIT_NULLABLE
		static RaisedEvent<EventArgs>? RaisesInternal(
#else
		static RaisedEvent<EventArgs> RaisesInternal(
#endif
			Action<EventHandler> attach,
			Action<EventHandler> detach,
			Action testCode)
		{
			GuardArgumentNotNull(nameof(attach), attach);
			GuardArgumentNotNull(nameof(detach), detach);
			GuardArgumentNotNull(nameof(testCode), testCode);

			var raisedEvent = default(RaisedEvent<EventArgs>);
#if XUNIT_NULLABLE
			void handler(object? s, EventArgs args) => raisedEvent = new RaisedEvent<EventArgs>(s, args);
#else
			EventHandler handler = (object s, EventArgs args) => raisedEvent = new RaisedEvent<EventArgs>(s, args);
#endif
			attach(handler);
			testCode();
			detach(handler);
			return raisedEvent;
		}

#if XUNIT_NULLABLE
		static RaisedEvent<T>? RaisesInternal<T>(
#else
		static RaisedEvent<T> RaisesInternal<T>(
#endif
			Action<Action<T>> attach,
			Action<Action<T>> detach,
			Action testCode)
		{
			var raisedEvent = default(RaisedEvent<T>);
			Action<T> handler = (T args) => raisedEvent = new RaisedEvent<T>(args);

			return RaisesInternal(
				() => raisedEvent,
				() => attach(handler),
				() => detach(handler),
				testCode);
		}

#if XUNIT_NULLABLE
		static RaisedEvent<T>? RaisesInternal<T>(
#else
		static RaisedEvent<T> RaisesInternal<T>(
#endif
			Action<EventHandler<T>> attach,
			Action<EventHandler<T>> detach,
			Action testCode)
		{
			var raisedEvent = default(RaisedEvent<T>);
#if XUNIT_NULLABLE
			void handler(object? s, T args) => raisedEvent = new RaisedEvent<T>(s, args);
#else
			EventHandler<T> handler = (object s, T args) => raisedEvent = new RaisedEvent<T>(s, args);
#endif
			return RaisesInternal(
				() => raisedEvent,
				() => attach(handler),
				() => detach(handler),
				testCode);
		}

#if XUNIT_NULLABLE
		static RaisedEvent<T>? RaisesInternal<T>(
			Func<RaisedEvent<T>?> handler,
#else
		static RaisedEvent<T> RaisesInternal<T>(
			Func<RaisedEvent<T>> handler,
#endif
			Action attach,
			Action detach,
			Action testCode)
		{
			GuardArgumentNotNull(nameof(handler), handler);
			GuardArgumentNotNull(nameof(attach), attach);
			GuardArgumentNotNull(nameof(detach), detach);
			GuardArgumentNotNull(nameof(testCode), testCode);

			attach();
			testCode();
			detach();
			return handler();
		}

		static async Task<bool> RaisesAsyncInternal(
			Action<Action> attach,
			Action<Action> detach,
			Func<Task> testCode)
		{
			GuardArgumentNotNull(nameof(attach), attach);
			GuardArgumentNotNull(nameof(detach), detach);
			GuardArgumentNotNull(nameof(testCode), testCode);

			var result = false;
			Action handler = () => result = true;

			attach(handler);
			await testCode();
			detach(handler);
			return result;
		}

#if XUNIT_NULLABLE
		static async Task<RaisedEvent<EventArgs>?> RaisesAsyncInternal(
#else
		static async Task<RaisedEvent<EventArgs>> RaisesAsyncInternal(
#endif
			Action<EventHandler> attach,
			Action<EventHandler> detach,
			Func<Task> testCode)
		{
			GuardArgumentNotNull(nameof(attach), attach);
			GuardArgumentNotNull(nameof(detach), detach);
			GuardArgumentNotNull(nameof(testCode), testCode);

			var raisedEvent = default(RaisedEvent<EventArgs>);
#if XUNIT_NULLABLE
			void handler(object? s, EventArgs args) => raisedEvent = new RaisedEvent<EventArgs>(s, args);
#else
			EventHandler handler = (object s, EventArgs args) => raisedEvent = new RaisedEvent<EventArgs>(s, args);
#endif
			attach(handler);
			await testCode();
			detach(handler);
			return raisedEvent;
		}

#if XUNIT_NULLABLE
		static async Task<RaisedEvent<T>?> RaisesAsyncInternal<T>(
#else
		static async Task<RaisedEvent<T>> RaisesAsyncInternal<T>(
#endif
			Action<Action<T>> attach,
			Action<Action<T>> detach,
			Func<Task> testCode)
		{
			GuardArgumentNotNull(nameof(attach), attach);
			GuardArgumentNotNull(nameof(detach), detach);
			GuardArgumentNotNull(nameof(testCode), testCode);

			var raisedEvent = default(RaisedEvent<T>);
			Action<T> handler = (T args) => raisedEvent = new RaisedEvent<T>(args);

			attach(handler);
			await testCode();
			detach(handler);
			return raisedEvent;
		}

#if XUNIT_NULLABLE
		static async Task<RaisedEvent<T>?> RaisesAsyncInternal<T>(
#else
		static async Task<RaisedEvent<T>> RaisesAsyncInternal<T>(
#endif
			Action<EventHandler<T>> attach,
			Action<EventHandler<T>> detach,
			Func<Task> testCode)
		{
			GuardArgumentNotNull(nameof(attach), attach);
			GuardArgumentNotNull(nameof(detach), detach);
			GuardArgumentNotNull(nameof(testCode), testCode);

			var raisedEvent = default(RaisedEvent<T>);
#if XUNIT_NULLABLE
			void handler(object? s, T args) => raisedEvent = new RaisedEvent<T>(s, args);
#else
			EventHandler<T> handler = (object s, T args) => raisedEvent = new RaisedEvent<T>(s, args);
#endif
			attach(handler);
			await testCode();
			detach(handler);
			return raisedEvent;
		}

		/// <summary>
		/// Represents a raised event after the fact.
		/// </summary>
		/// <typeparam name="T">The type of the event arguments.</typeparam>
		public class RaisedEvent<T>
		{
			/// <summary>
			/// The sender of the event. When the event is recorded via <see cref="Action{T}"/> rather
			/// than <see cref="EventHandler{TEventArgs}"/>, this value will always be <c>null</c>,
			/// since there is no sender value when using actions.
			/// </summary>
#if XUNIT_NULLABLE
			public object? Sender { get; }
#else
			public object Sender { get; }
#endif

			/// <summary>
			/// The event arguments.
			/// </summary>
			public T Arguments { get; }

			/// <summary>
			/// Creates a new instance of the <see cref="RaisedEvent{T}" /> class.
			/// </summary>
			/// <param name="args">The event arguments</param>
			public RaisedEvent(T args) :
				this(null, args)
			{ }

			/// <summary>
			/// Creates a new instance of the <see cref="RaisedEvent{T}" /> class.
			/// </summary>
			/// <param name="sender">The sender of the event.</param>
			/// <param name="args">The event arguments</param>
			public RaisedEvent(
#if XUNIT_NULLABLE
				object? sender,
#else
				object sender,
#endif
				T args)
			{
				Sender = sender;
				Arguments = args;
			}
		}
	}
}