IVSoftware.Portable.Threading
1.2.0
Prefix Reserved
See the version list below for details.
dotnet add package IVSoftware.Portable.Threading --version 1.2.0
NuGet\Install-Package IVSoftware.Portable.Threading -Version 1.2.0
<PackageReference Include="IVSoftware.Portable.Threading" Version="1.2.0" />
paket add IVSoftware.Portable.Threading --version 1.2.0
#r "nuget: IVSoftware.Portable.Threading, 1.2.0"
// Install IVSoftware.Portable.Threading as a Cake Addin #addin nuget:?package=IVSoftware.Portable.Threading&version=1.2.0 // Install IVSoftware.Portable.Threading as a Cake Tool #tool nuget:?package=IVSoftware.Portable.Threading&version=1.2.0
This package addresses a need that is crucial and common in a test (e.g. MSTest) environment where evaluating asynchronous UI interactions in something like a WPF or Winforms app is often complex and fraught with challenges. These tests might involve stimuli that are either test-driven or interactively user-driven. They may also require monitoring for changes in typically synchronous methods like OnPropertyChanged, or tracking updates in a continuously running polling loop.
I saw the question recently worded as How can async void
methods be tested? Or, to put a finer point on it, how can we await the unawaitable?
This is a tried and true approach that I've used extensively for testing my UI application in "just the basic" MSTest environment. My early attempts always seemed to pile additional timing uncertainties on top of the ones I was trying to test. This solution is dirt simple. I made a helper class that exposes an extension for object
that fires a custom static event automatically tagged with the caller method name. There's also an args property that can carry a Dictionary<string, object> or a json payload (for example), and this is to provide context to the MSTest method that is listening to it, plus you have the sender object itself. Taken together, this provides a rich context in which to evaluate this moment in the app's asynchronous life.
One of the simplest examples I can think of would be the ability to await an expected property change from within the synchronous System.Windows.Window.OnPropertyChanged()
method in the app under test. You can do this by adding one line to call the OnAwaited
extension:
App under test
// <PackageReference Include="IVSoftware.Portable.Threading" Version="*" />
// using IVSoftware.Portable.Threading;
// The synchronous method you want to observe but can't (or shouldn't) call directly.
protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
{
base.OnPropertyChanged(e);
this.OnAwaited(new AwaitedEventArgs(args: new Dictionary<string, object>
{
{ nameof(DependencyPropertyChangedEventArgs), e }
}));
}
MSTest
In the test method, the static Awaited
event is subscribed just for the duration of this particular test. Once its raised, it can be inspected to see who the sending object is, to see what method actually called it, and to examine whatever rich treasures have been pushed into the args object for detailed analysis.
// using static IVSoftware.Portable.Threading.Extensions;
[TestMethod]
public async Task MyTest_ReturnsData()
{
SemaphoreSlim awaiter = new SemaphoreSlim(0, 1);
string? actual = null;
try
{
Awaited += localOnAwaited;
WPFAppWindow?.CallSomeAsyncVoidMethod();
// Wait for it to have a deterministic
// effect after a non-deteministic time.
Assert.IsTrue(
condition: await awaiter.WaitAsync(timeout: TimeSpan.FromSeconds(10)),
"Timed out waiting for property change.");
Assert.AreEqual(
expected: "MyExpectedValue",
actual: actual,
$"An unexpected value was detected in {nameof(WPFAppWindow)}.OnPropertyChanged().");
}
finally
{
// CRITICAL to unconditionally unsubscribe
// from the static method when done.
Awaited -= localOnAwaited;
}
#region L o c a l M e t h o d s
void localOnAwaited(object? sender, AwaitedEventArgs e)
{
object? o;
switch (e.Caller)
{
// Very common scenario of listening for a
// property to change after a UI stimulus.
case "OnPropertyChanged":
if (e.Args is Dictionary<string, object> args)
{
if (args.TryGetValue(nameof(DependencyPropertyChangedEventArgs), out o) &&
o is DependencyPropertyChangedEventArgs wpfPropertyChanged)
{
switch (wpfPropertyChanged.Property.Name)
{
case "MyTargetProperty":
// The property we've been listening to has changed.
actual = $"{wpfPropertyChanged.NewValue}";
awaiter.Release();
break;
}
}
}
break;
}
}
#endregion L o c a l M e t h o d s
}
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. |
.NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
.NET Standard | netstandard2.0 is compatible. netstandard2.1 was computed. |
.NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
MonoAndroid | monoandroid was computed. |
MonoMac | monomac was computed. |
MonoTouch | monotouch was computed. |
Tizen | tizen40 was computed. tizen60 was computed. |
Xamarin.iOS | xamarinios was computed. |
Xamarin.Mac | xamarinmac was computed. |
Xamarin.TVOS | xamarintvos was computed. |
Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.0
- No dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
Version | Downloads | Last updated |
---|---|---|
1.3.0-preview3 | 89 | 9/22/2024 |
1.2.0 | 109 | 9/12/2024 |
1.1.0 | 96 | 9/9/2024 |
- Updated README with a more streamlined example.
- Added a demonstration WPF Application and an MSTest routine to exercise it.
- MSTest integration provides a practical test routine to validate behavior with the WPF demo.