Files
Sergio Pedri 12ae36b594 Refactor unit tests to use new assertion helpers
Replaces usages of Assert.AreEqual, Assert.IsTrue, and similar assertions with new helper methods such as Assert.HasCount, Assert.IsEmpty, Assert.IsNull, and Assert.IsGreaterThanOrEqualTo across multiple test files. This improves test readability and consistency with updated assertion APIs.
2025-11-09 18:34:09 -08:00

608 lines
20 KiB
C#

// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Threading.Tasks;
using System.Windows.Input;
using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.UnitTests.Helpers;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Nito.AsyncEx;
namespace CommunityToolkit.Mvvm.UnitTests;
[TestClass]
public class Test_AsyncRelayCommandOfT
{
[TestMethod]
public async Task Test_AsyncRelayCommandOfT_AlwaysEnabled()
{
int ticks = 0;
AsyncRelayCommand<string>? command = new(async s =>
{
await Task.Delay(1000);
ticks = int.Parse(s!);
await Task.Delay(1000);
});
Assert.IsTrue(command.CanExecute(null));
Assert.IsTrue(command.CanExecute("1"));
(object?, EventArgs?) args = default;
command.CanExecuteChanged += (s, e) => args = (s, e);
command.NotifyCanExecuteChanged();
Assert.AreSame(args.Item1, command);
Assert.AreSame(args.Item2, EventArgs.Empty);
Assert.IsNull(command.ExecutionTask);
Assert.IsFalse(command.IsRunning);
Task task = command.ExecuteAsync((object)"42");
Assert.IsNotNull(command.ExecutionTask);
Assert.AreSame(command.ExecutionTask, task);
Assert.IsTrue(command.IsRunning);
await task;
Assert.IsFalse(command.IsRunning);
Assert.AreEqual(42, ticks);
command.Execute("2");
await command.ExecutionTask!;
Assert.AreEqual(2, ticks);
ExceptionHelper.ThrowsArgumentExceptionWithParameterName(() => command.CanExecute(new object()), "parameter");
ExceptionHelper.ThrowsArgumentExceptionWithParameterName(() => command.CanExecute(42), "parameter");
ExceptionHelper.ThrowsArgumentExceptionWithParameterName(() => command.Execute(new object()), "parameter");
ExceptionHelper.ThrowsArgumentExceptionWithParameterName(() => command.Execute(42), "parameter");
}
[TestMethod]
public void Test_AsyncRelayCommandOfT_WithCanExecuteFunctionTrue()
{
int ticks = 0;
AsyncRelayCommand<string>? command = new(
s =>
{
ticks = int.Parse(s!);
return Task.CompletedTask;
}, s => true);
Assert.IsTrue(command.CanExecute(null));
Assert.IsTrue(command.CanExecute("1"));
command.Execute("42");
Assert.AreEqual(42, ticks);
command.Execute("2");
Assert.AreEqual(2, ticks);
ExceptionHelper.ThrowsArgumentExceptionWithParameterName(() => command.CanExecute(new object()), "parameter");
ExceptionHelper.ThrowsArgumentExceptionWithParameterName(() => command.CanExecute(42), "parameter");
ExceptionHelper.ThrowsArgumentExceptionWithParameterName(() => command.Execute(new object()), "parameter");
ExceptionHelper.ThrowsArgumentExceptionWithParameterName(() => command.Execute(42), "parameter");
}
[TestMethod]
public void Test_AsyncRelayCommandOfT_WithCanExecuteFunctionFalse()
{
int ticks = 0;
AsyncRelayCommand<string>? command = new(
s =>
{
ticks = int.Parse(s!);
return Task.CompletedTask;
}, s => false);
Assert.IsFalse(command.CanExecute(null));
Assert.IsFalse(command.CanExecute("1"));
command.Execute("2");
// Like in the RelayCommand test, ensure Execute is unconditionally invoked
Assert.AreEqual(2, ticks);
command.Execute("42");
Assert.AreEqual(42, ticks);
ExceptionHelper.ThrowsArgumentExceptionWithParameterName(() => command.CanExecute(new object()), "parameter");
ExceptionHelper.ThrowsArgumentExceptionWithParameterName(() => command.CanExecute(42), "parameter");
ExceptionHelper.ThrowsArgumentExceptionWithParameterName(() => command.Execute(new object()), "parameter");
ExceptionHelper.ThrowsArgumentExceptionWithParameterName(() => command.Execute(42), "parameter");
}
[TestMethod]
public void Test_AsyncRelayCommandOfT_InvalidArgumentWithValueType()
{
int n = 0;
AsyncRelayCommand<int>? command = new(i =>
{
n = i;
return Task.CompletedTask;
});
// Special case
Assert.IsFalse(command.CanExecute(null));
ExceptionHelper.ThrowsArgumentExceptionWithParameterName(() => command.CanExecute("Hello"), "parameter");
ExceptionHelper.ThrowsArgumentExceptionWithParameterName(() => command.CanExecute(3.14f), "parameter");
ExceptionHelper.ThrowsArgumentExceptionWithParameterName(() => command.Execute(null), "parameter");
ExceptionHelper.ThrowsArgumentExceptionWithParameterName(() => command.Execute("Hello"), "parameter");
ExceptionHelper.ThrowsArgumentExceptionWithParameterName(() => command.Execute(3.14f), "parameter");
}
[TestMethod]
public void Test_AsyncRelayCommandOfT_InvalidArgumentWithValueType_WithCanExecute()
{
int n = 0;
AsyncRelayCommand<int>? command = new(
i =>
{
n = i;
return Task.CompletedTask;
}, i => i > 0);
// Special case
Assert.IsFalse(command.CanExecute(null));
ExceptionHelper.ThrowsArgumentExceptionWithParameterName(() => command.CanExecute("Hello"), "parameter");
ExceptionHelper.ThrowsArgumentExceptionWithParameterName(() => command.CanExecute(3.14f), "parameter");
ExceptionHelper.ThrowsArgumentExceptionWithParameterName(() => command.Execute(null), "parameter");
ExceptionHelper.ThrowsArgumentExceptionWithParameterName(() => command.Execute("Hello"), "parameter");
ExceptionHelper.ThrowsArgumentExceptionWithParameterName(() => command.Execute(3.14f), "parameter");
}
[TestMethod]
public async Task Test_AsyncRelayCommandOfT_WithCancellation()
{
// See comments in Test_AsyncRelayCommand_WithCancellation for the logic below
TaskCompletionSource<object?> tcs = new();
AsyncRelayCommand<string> command = new((s, token) => tcs.Task);
List<PropertyChangedEventArgs> args = new();
command.PropertyChanged += (s, e) => args.Add(e);
Assert.IsTrue(command.CanExecute(null));
Assert.IsTrue(command.CanExecute("Hello"));
Assert.IsFalse(command.CanBeCanceled);
Assert.IsFalse(command.IsCancellationRequested);
command.Execute(null);
Assert.IsTrue(command.CanBeCanceled);
Assert.IsFalse(command.IsCancellationRequested);
Assert.HasCount(4, args);
Assert.AreEqual(nameof(IAsyncRelayCommand.ExecutionTask), args[0].PropertyName);
Assert.AreEqual(nameof(IAsyncRelayCommand.IsRunning), args[1].PropertyName);
Assert.AreEqual(nameof(IAsyncRelayCommand.CanBeCanceled), args[2].PropertyName);
Assert.AreEqual(nameof(IAsyncRelayCommand.IsCancellationRequested), args[3].PropertyName);
command.Cancel();
Assert.HasCount(6, args);
Assert.AreEqual(nameof(IAsyncRelayCommand.CanBeCanceled), args[4].PropertyName);
Assert.AreEqual(nameof(IAsyncRelayCommand.IsCancellationRequested), args[5].PropertyName);
Assert.IsTrue(command.IsCancellationRequested);
tcs.SetResult(null);
await command.ExecutionTask!;
Assert.IsFalse(command.CanBeCanceled);
Assert.IsTrue(command.IsCancellationRequested);
}
[TestMethod]
public async Task Test_AsyncRelayCommandOfT_AllowConcurrentExecutions_Disabled()
{
await Test_AsyncRelayCommandOfT_AllowConcurrentExecutions_TestLogic(static task => new(async _ => await task, AsyncRelayCommandOptions.None));
}
[TestMethod]
public async Task Test_AsyncRelayCommandOfT_AllowConcurrentExecutions_Default()
{
await Test_AsyncRelayCommandOfT_AllowConcurrentExecutions_TestLogic(static task => new(async _ => await task));
}
/// <summary>
/// Shared logic for <see cref="Test_AsyncRelayCommandOfT_AllowConcurrentExecutions_Disabled"/> and <see cref="Test_AsyncRelayCommandOfT_AllowConcurrentExecutions_Default"/>.
/// </summary>
/// <param name="factory">A factory to create the <see cref="AsyncRelayCommand{T}"/> instance to test.</param>
private static async Task Test_AsyncRelayCommandOfT_AllowConcurrentExecutions_TestLogic(Func<Task, AsyncRelayCommand<string>> factory)
{
TaskCompletionSource<object?> tcs = new();
AsyncRelayCommand<string> command = factory(tcs.Task);
Assert.IsTrue(command.CanExecute(null));
Assert.IsTrue(command.CanExecute("1"));
(object?, EventArgs?) args = default;
command.CanExecuteChanged += (s, e) => args = (s, e);
command.NotifyCanExecuteChanged();
Assert.AreSame(args.Item1, command);
Assert.AreSame(args.Item2, EventArgs.Empty);
Assert.IsNull(command.ExecutionTask);
Assert.IsFalse(command.IsRunning);
Task task = command.ExecuteAsync((object)"42");
Assert.IsNotNull(command.ExecutionTask);
Assert.AreSame(command.ExecutionTask, task);
Assert.IsTrue(command.IsRunning);
// The command can't be executed now, as there's a pending operation
Assert.IsFalse(command.CanExecute(null));
Assert.IsFalse(command.CanExecute("2"));
Assert.IsFalse(command.CanBeCanceled);
Assert.IsFalse(command.IsCancellationRequested);
tcs.SetResult(null);
await task;
Assert.IsFalse(command.IsRunning);
ExceptionHelper.ThrowsArgumentExceptionWithParameterName(() => command.CanExecute(new object()), "parameter");
ExceptionHelper.ThrowsArgumentExceptionWithParameterName(() => command.CanExecute(3.14f), "parameter");
ExceptionHelper.ThrowsArgumentExceptionWithParameterName(() => command.Execute(new object()), "parameter");
ExceptionHelper.ThrowsArgumentExceptionWithParameterName(() => command.Execute(3.14f), "parameter");
}
[TestMethod]
public void Test_AsyncRelayCommandOfT_EnsureExceptionThrown_Synchronously()
{
Exception? executeException = null;
AsyncRelayCommand<int> command = new(async delay =>
{
await Task.CompletedTask;
throw new Exception(nameof(Test_AsyncRelayCommandOfT_EnsureExceptionThrown_Synchronously));
});
try
{
AsyncContext.Run(async () =>
{
command.Execute((object)42);
await Task.Delay(500);
});
}
catch (Exception e)
{
executeException = e;
}
Assert.AreEqual(nameof(Test_AsyncRelayCommandOfT_EnsureExceptionThrown_Synchronously), executeException?.Message);
}
// See https://github.com/CommunityToolkit/dotnet/pull/251
[TestMethod]
public async Task Test_AsyncRelayCommandOfT_EnsureExceptionThrown()
{
const int delay = 500;
Exception? executeException = null;
Exception? executeAsyncException = null;
AsyncRelayCommand<int> command = new(async delay =>
{
await Task.Delay(delay);
throw new Exception(nameof(Test_AsyncRelayCommandOfT_EnsureExceptionThrown));
});
try
{
AsyncContext.Run(async () =>
{
command.Execute((object)delay);
await Task.Delay(delay * 2);
});
}
catch (Exception e)
{
executeException = e;
}
executeAsyncException = await Assert.ThrowsExactlyAsync<Exception>(() => command.ExecuteAsync((object)delay));
Assert.AreEqual(nameof(Test_AsyncRelayCommandOfT_EnsureExceptionThrown), executeException?.Message);
Assert.AreEqual(nameof(Test_AsyncRelayCommandOfT_EnsureExceptionThrown), executeAsyncException?.Message);
}
[TestMethod]
public async Task Test_AsyncRelayCommandOfT_EnsureExceptionThrown_GenericExecute()
{
const int delay = 500;
Exception? executeTException = null;
Exception? executeAsyncException = null;
AsyncRelayCommand<int> command = new(async delay =>
{
await Task.Delay(delay);
throw new Exception(nameof(Test_AsyncRelayCommandOfT_EnsureExceptionThrown_GenericExecute));
});
try
{
AsyncContext.Run(async () =>
{
command.Execute(delay);
await Task.Delay(delay * 2);
});
}
catch (Exception e)
{
executeTException = e;
}
executeAsyncException = await Assert.ThrowsExactlyAsync<Exception>(() => command.ExecuteAsync(delay));
Assert.AreEqual(nameof(Test_AsyncRelayCommandOfT_EnsureExceptionThrown_GenericExecute), executeTException?.Message);
Assert.AreEqual(nameof(Test_AsyncRelayCommandOfT_EnsureExceptionThrown_GenericExecute), executeAsyncException?.Message);
}
[TestMethod]
public async Task Test_AsyncRelayCommandOfT_ThrowingTaskBubblesToUnobservedTaskException()
{
static async Task TestMethodAsync(Action action)
{
await Task.Delay(100);
action();
}
async void TestCallback(Action throwAction, Action completeAction)
{
AsyncRelayCommand<string> command = new(s => TestMethodAsync(throwAction), AsyncRelayCommandOptions.FlowExceptionsToTaskScheduler);
command.Execute(null);
await Task.Delay(200);
completeAction();
}
bool success = await TaskSchedulerTestHelper.IsExceptionBubbledUpToUnobservedTaskExceptionAsync(TestCallback);
Assert.IsTrue(success);
}
[TestMethod]
public async Task Test_AsyncRelayCommandOfT_ThrowingTaskBubblesToUnobservedTaskException_Synchronously()
{
static async Task TestMethodAsync(Action action)
{
await Task.CompletedTask;
action();
}
async void TestCallback(Action throwAction, Action completeAction)
{
AsyncRelayCommand<string> command = new(s => TestMethodAsync(throwAction), AsyncRelayCommandOptions.FlowExceptionsToTaskScheduler);
command.Execute(null);
await Task.Delay(200);
completeAction();
}
bool success = await TaskSchedulerTestHelper.IsExceptionBubbledUpToUnobservedTaskExceptionAsync(TestCallback);
Assert.IsTrue(success);
}
public async Task Test_AsyncRelayCommand_ExecuteDoesNotRaiseCanExecuteChanged()
{
TaskCompletionSource<object?> tcs = new();
AsyncRelayCommand<string> command = new(s => tcs.Task, AsyncRelayCommandOptions.AllowConcurrentExecutions);
(object? Sender, EventArgs? Args) args = default;
command.CanExecuteChanged += (s, e) => args = (s, e);
Assert.IsTrue(command.CanExecute(""));
command.Execute("");
Assert.IsNull(args.Sender);
Assert.IsNull(args.Args);
args = default;
Assert.IsTrue(command.CanExecute(""));
tcs.SetResult(null);
_ = await tcs.Task;
// CanExecute isn't raised when the command completes
Assert.IsNull(args.Sender);
Assert.IsNull(args.Args);
}
[TestMethod]
public async Task Test_AsyncRelayCommand_ExecuteWithoutConcurrencyRaisesCanExecuteChanged()
{
TaskCompletionSource<object?> tcs = new();
AsyncRelayCommand<string> command = new(s => tcs.Task, AsyncRelayCommandOptions.None);
(object? Sender, EventArgs? Args) args = default;
command.CanExecuteChanged += (s, e) => args = (s, e);
Assert.IsTrue(command.CanExecute(""));
command.Execute("");
Assert.AreSame(command, args.Sender);
Assert.AreSame(EventArgs.Empty, args.Args);
Assert.IsFalse(command.CanExecute(""));
args = default;
tcs.SetResult(null);
_ = await tcs.Task;
// CanExecute is raised again when the command completes
Assert.AreSame(command, args.Sender);
Assert.AreSame(EventArgs.Empty, args.Args);
}
[TestMethod]
public void Test_AsyncRelayCommand_ExecuteDoesNotRaiseCanExecuteChanged_WithCancellation()
{
TaskCompletionSource<object?> tcs = new();
AsyncRelayCommand<string> command = new((s, token) => tcs.Task, AsyncRelayCommandOptions.AllowConcurrentExecutions);
(object? Sender, EventArgs? Args) args = default;
command.CanExecuteChanged += (s, e) => args = (s, e);
Assert.IsTrue(command.CanExecute(""));
command.Execute("");
Assert.IsNull(args.Sender);
Assert.IsNull(args.Args);
Assert.IsTrue(command.CanExecute(""));
tcs.SetResult(null);
}
[TestMethod]
public void Test_AsyncRelayCommand_ExecuteWithoutConcurrencyRaisesCanExecuteChanged_WithToken()
{
TaskCompletionSource<object?> tcs = new();
AsyncRelayCommand<string> command = new((s, token) => tcs.Task, AsyncRelayCommandOptions.None);
(object? Sender, EventArgs? Args) args = default;
command.CanExecuteChanged += (s, e) => args = (s, e);
Assert.IsTrue(command.CanExecute(""));
command.Execute("");
Assert.AreSame(command, args.Sender);
Assert.AreSame(EventArgs.Empty, args.Args);
Assert.IsFalse(command.CanExecute(""));
tcs.SetResult(null);
}
[TestMethod]
public void Test_AsyncRelayCommandOfT_GetCancelCommand_DisabledCommand()
{
TaskCompletionSource<object?> tcs = new();
AsyncRelayCommand<string> command = new(s => tcs.Task);
ICommand cancelCommand = command.CreateCancelCommand();
Assert.IsNotNull(cancelCommand);
Assert.IsFalse(cancelCommand.CanExecute(null));
// No-op
cancelCommand.Execute(null);
Assert.AreEqual("CommunityToolkit.Mvvm.Input.Internals.DisabledCommand", cancelCommand.GetType().ToString());
ICommand cancelCommand2 = command.CreateCancelCommand();
Assert.IsNotNull(cancelCommand2);
Assert.IsFalse(cancelCommand2.CanExecute(null));
Assert.AreSame(cancelCommand, cancelCommand2);
}
[TestMethod]
public void Test_AsyncRelayCommandOfT_GetCancelCommand_WithToken()
{
TaskCompletionSource<object?> tcs = new();
AsyncRelayCommand<string> command = new((s, token) => tcs.Task);
ICommand cancelCommand = command.CreateCancelCommand();
Assert.IsNotNull(cancelCommand);
Assert.IsFalse(cancelCommand.CanExecute(null));
// No-op
cancelCommand.Execute(null);
Assert.AreEqual("CommunityToolkit.Mvvm.Input.Internals.CancelCommand", cancelCommand.GetType().ToString());
List<(object? Sender, EventArgs Args)> cancelCommandCanExecuteChangedArgs = new();
cancelCommand.CanExecuteChanged += (s, e) => cancelCommandCanExecuteChangedArgs.Add((s, e));
command.Execute(null);
Assert.HasCount(1, cancelCommandCanExecuteChangedArgs);
Assert.AreSame(cancelCommand, cancelCommandCanExecuteChangedArgs[0].Sender);
Assert.AreSame(EventArgs.Empty, cancelCommandCanExecuteChangedArgs[0].Args);
Assert.IsTrue(cancelCommand.CanExecute(null));
cancelCommand.Execute(null);
Assert.IsFalse(cancelCommand.CanExecute(null));
Assert.HasCount(2, cancelCommandCanExecuteChangedArgs);
Assert.AreSame(cancelCommand, cancelCommandCanExecuteChangedArgs[1].Sender);
Assert.AreSame(EventArgs.Empty, cancelCommandCanExecuteChangedArgs[1].Args);
Assert.IsFalse(command.CanBeCanceled);
Assert.IsTrue(command.IsCancellationRequested);
}
}