I am test driving a class that gets injected with a bunch of work tasks, runs the asynchronously and restarts them when completed until told to halt all tasks.
Since I am doing test first I needed to write a test that forces me to write the restart logic, and I have kind of successfully done this, but I don't think I did it very well.
Test code: (FakeTask is basically a test spy that keeps track on whether it was called and how many times)
[Fact]
public async void Start_GivenTask_RerunsTaskUntilStopped()
{
var agent = CreateKlarnaAgent();
var fakeTask = DoNothingTask();
agent.Start(fakeTask);
Thread.Sleep(500);
await agent.Stop();
Assert.True(fakeTask.TimesRun > 1);
}
(Relevant) production code:
public void Start(params IWorkTask[] workTasks)
{
_logWriter.Debug("Starting...");
_tasks = workTasks
.Select(workTask => workTask.DoWork().ContinueWith(task => OnTaskComplete(task, workTask)))
.ToArray();
}
private void OnTaskComplete(Task completedTask, IWorkTask workTask)
{
if (completedTask.IsFaulted)
{
foreach (var exception in completedTask.Exception.InnerExceptions)
{
_logWriter.Error("Unhandled exception thrown!", exception);
}
}
else workTask.DoWork().ContinueWith(task => OnTaskComplete(task, workTask));
}
public Task Stop()
{
return Task.WhenAll(_tasks)
.ContinueWith(t => { _logWriter.Debug("Stopped"); });
}
The test is now really depending on a race condition and it doesn't feel like a unit test at all. How can I get rid of the Thread.Sleep(500) call? Or is this simply something I should test in an integration test?
On a side note, I recommend against writing "task runners" in general, and also recommend against ContinueWith in particular since it is such a dangerous API.
In my opinion, the "repeat forever until canceled" logic is far more clearly expressed using a loop for "repeat" and a cancellation token for "canceled":
static async Task WorkAsync(Func<Task> doWork, CancellationToken token)
{
while (true)
{
await doWork();
token.ThrowIfCancellationRequested();
}
}
However, that said, if you want to unit test your "task runner" as-is, you'll need to make your FakeTask more intelligent. For example, you could have it set a signal when it reaches a given count and have your unit test wait on that:
class FakeTask : IWorkTask
{
private readonly TaskCompletionSource<object> _done = new TaskCompletionSource<object>();
public Task Done { get { return _done.Task; } }
public Task DoWork()
{
++TimesRun;
if (TimesRun > 1)
_done.TrySetResult(null);
return Task.CompletedTask;
}
}
[Fact]
public async Task Start_GivenTask_RerunsTaskUntilStopped()
{
var agent = CreateKlarnaAgent();
var fakeTask = DoNothingTask();
agent.Start(fakeTask);
await fakeTask.Done;
await agent.Stop();
Assert.True(fakeTask.TimesRun > 1); // spurious test at this point
}
Related
How do you wait until a function with a task inside is done before continuing?
public void A()
{
Debug.Log("before")
CopyInfoFromDB();
Debug.Log("after")
}
public void CopyInfoFromDB()
{
FirebaseDatabase.DefaultInstance.GetReference(path)
.GetValueAsync().ContinueWith(task =>
{
if (task.IsFaulted)
{
Debug.Log("failed");
}
name = ...// loading local varibles from Task.result
});
}
I want it to wait for CopyInfoFromDB to be completed before printing "after". How should I write function A differently?
If you are going to use async-await - be prepared to make all methods involved in pipeline to be asynchronous as well
public async Task A()
{
Debug.Log("before")
await CopyInfoFromDB();
Debug.Log("after")
}
public Task CopyInfoFromDB()
{
return FirebaseDatabase.DefaultInstance
.GetReference(path)
.GetValueAsync();
}
In case GetValueAsync fails, exception will be thrown on the line where you are awaiting for it.
I have a simple class that implements INotifyPropertyChanged, I invoke the property change on another thread, and I had a pretty hard time getting FluentAsserts to see that the propertyChanged was invoked. It does not seem to happen if I use a Task.Delay in an async Task method. But it does if I just sleep the thread.
The SimpleNotify class:
namespace FluentAssertPropertyThreads
{
class SimpleNotify : System.ComponentModel.INotifyPropertyChanged
{
public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;
private void onChange(string name)
{
this.PropertyChanged?.Invoke(this, new System.ComponentModel.PropertyChangedEventArgs(name));
}
private int count = 0;
public int Count
{
get
{
return this.count;
}
set
{
if (this.count != value)
{
this.count = value;
this.onChange(nameof(this.Count));
}
}
}
}
}
and here are my unit tests:
using FluentAssertions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Threading;
using System.Threading.Tasks;
namespace FluentAssertPropertyThreads
{
[TestClass]
public class UnitTest1
{
private SimpleNotify simpleNotify;
private int notifyCount;
private void bumpCount()
{
this.simpleNotify.Count++;
}
private void SimpleNotify_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
SimpleNotify simpleNotify = sender as SimpleNotify;
if (simpleNotify == null)
{
throw new ArgumentNullException(nameof(sender), "sender should be " + nameof(SimpleNotify));
}
if (e.PropertyName.Equals(nameof(SimpleNotify.Count), StringComparison.InvariantCultureIgnoreCase))
{
this.notifyCount++;
}
}
[TestInitialize]
public void TestSetup()
{
this.notifyCount = 0;
this.simpleNotify = new SimpleNotify();
this.simpleNotify.PropertyChanged += SimpleNotify_PropertyChanged;
this.simpleNotify.MonitorEvents();
Thread thread = new Thread(this.bumpCount)
{
IsBackground = true,
Name = #"My Background Thread",
Priority = ThreadPriority.Normal
};
thread.Start();
}
[TestMethod]
public async Task TestMethod1()
{
await Task.Delay(100);
this.notifyCount.Should().Be(1); //this passes, so I know that my notification has be executed.
this.simpleNotify.ShouldRaisePropertyChangeFor(x => x.Count); //but this fails, saying that I need to be monitoring the events (which I am above)
}
[TestMethod]
public void TestMethod2()
{
Thread.Sleep(100);
this.notifyCount.Should().Be(1); //this passes, so I know that my notification has be executed.
this.simpleNotify.ShouldRaisePropertyChangeFor(x => x.Count); //this passes as I expected
}
}
}
The exact error is:
System.InvalidOperationException: Object is not being monitored for events or has already been garbage collected. Use the MonitorEvents() extension method to start monitoring events.
I don't see how MonitorEvents would care if I use await or Thread.Sleep. What am I missing? I get that await leaves the method and comes back in, whereas Thread.Sleep does not.
So when it leaves the TestMethod1 during the await, it is hitting a dispose on an object that FluentAsserts is using to track the properties? Could it? Should it?
Yes, the things is like you said: await pauses the execution of current method and create a state machine to get back to the method after the Delay is done. But the caller (a UnitTest engine) doesn't expect your tests to be asynchronous and simply ends the execution, which leads to the disposal of the objects.
Stephen Cleary wrote a brilliant MSDN article about Unit testing and async/await keywords, you probably should move your code out to the method returning the Task and wait for all of it in test, something like this:
async Task Testing()
{
await Task.Delay(100);
this.notifyCount.Should().Be(1);
this.simpleNotify.ShouldRaisePropertyChangeFor(x => x.Count);
}
[TestMethod]
public async Task TestMethod1()
{
await Testing();
}
but this still may fail as the logic after await may execute after the disposable being disposed.
Looks like version 5.0.0 will include support for Async tests with monitoring.
This Issue was raised and the work completed just waiting on the documentation to be updated and version 5.0.0 to be released.
But for the time being there is a prerelease with the code changes 5.0.0-beta2 can get it from the prerelease versions on NuGet
From the change log:
{Breaking} Replaced the old thread-unsafe MonitorEvents API with a new
Monitor extension method that will return a thread-safe monitoring
scope that exposes methods like Should().Raise() and metadata such as
OccurredEvents and MonitoredEvents
So the code with the updated NuGet will look like this:
[TestInitialize]
public void TestSetup()
{
this.notifyCount = 0;
this.simpleNotify = new SimpleNotify();
this.simpleNotify.PropertyChanged += SimpleNotify_PropertyChanged;
Thread thread = new Thread(this.bumpCount)
{
IsBackground = true,
Name = #"My Background Thread",
Priority = ThreadPriority.Normal
};
thread.Start();
}
[TestMethod]
public async Task TestMethod1()
{
using (var MonitoredSimpleNotify = this.simpleNotify.Monitor())
{
await Task.Delay(100);
this.notifyCount.Should().Be(1);
MonitoredSimpleNotify.Should().RaisePropertyChangeFor(x => x.Count); // New API for Monitoring
}
}
As there is no RelayCommandAsync (at least not that I know of), how to test this scenario. For example:
public RelayCommand LoadJobCommand
{
get
{
return this.loadJobCommand ?? (
this.loadJobCommand =
new RelayCommand(
this.ExecuteLoadJobCommandAsync));
}
}
private async void ExecuteLoadJobCommandAsync()
{
await GetData(...);
}
Test:
vm.LoadJobCommand.Execute()
Assert.IsTrue(vm.Jobs.Count > 0)
It really depends on what you are trying to test:
Test that the RelayCommand is properly hooked up and calls your async
method?
or
Test that the Async Method logic is correct?
1. Testing the RelayCommand trigger
1.a Using External Dependencies to verify
From my personal experience the easiest way to test that the trigger is wired up correctly to execute the command and then test that your class has interacted with another external class somewhere as expected. E.g.
private async void ExecuteLoadJobCommandAsync()
{
await GetData(...);
}
private async void GetData(...)
{
var data = await _repo.GetData();
Jobs.Add(data);
}
Its fairly easy to test that your repo gets called.
public void TestUsingExternalDependency()
{
_repo.Setup(r => r.GetData())
.Returns(Task.Run(() => 5))
.Verifiable();
_vm.LoadJobCommand.Execute(null);
_repo.VerifyAll();
}
I sometimes even do this, so that it doesn't try to process everything:
[Test]
public void TestUsingExternalDependency()
{
_repo.Setup(r => r.GetData())
.Returns(() => { throw new Exception("TEST"); })
.Verifiable();
try
{
_vm.LoadJobCommand.Execute(null);
}
catch (Exception e)
{
e.Message.Should().Be("TEST");
}
_repo.VerifyAll();
}
1.b Using a Scheduler
Another option is to use a scheduler, and schedule tasks using that.
public interface IScheduler
{
void Execute(Action action);
}
// Injected when not under test
public class ThreadPoolScheduler : IScheduler
{
public void Execute(Action action)
{
Task.Run(action);
}
}
// Used for testing
public class ImmediateScheduler : IScheduler
{
public void Execute(Action action)
{
action();
}
}
Then in your ViewModel
public ViewModelUnderTest(IRepository repo, IScheduler scheduler)
{
_repo = repo;
_scheduler = scheduler;
LoadJobCommand = new RelayCommand(ExecuteLoadJobCommandAsync);
}
private void ExecuteLoadJobCommandAsync()
{
_scheduler.Execute(GetData);
}
private void GetData()
{
var a = _repo.GetData().Result;
Jobs.Add(a);
}
And your test
[Test]
public void TestUsingScheduler()
{
_repo.Setup(r => r.GetData()).Returns(Task.Run(() => 2));
_vm = new ViewModelUnderTest(_repo.Object, new ImmediateScheduler());
_vm.LoadJobCommand.Execute(null);
_vm.Jobs.Should().NotBeEmpty();
}
2. Testing the GetData Logic
If you are looking to test get GetData() logic or even the ExecuteLoadJobCommandAsync() logic. Then you should definitely make the method you want to test, as Internal, and mark your assmebly as InternalsVisibleTo so that you can call those methods directly from your test class.
Why don't you cover GetData(...) method with tests? I don't see any sense in testing relay commands
I was not using async/await but I have run in to a similar problem in the past. The situation I was in is the method called a Task.Run( inside of itself and the unit test was verifying that the ViewModel was calling the service with the correct number of times with the correct parameters.
The way we solved this was we had our Mock of the service that was being called use a ManualResetEventSlim, then the unit test waited for that reset event to be called before proceeding.
[TestMethod]
public void EXAMPLE()
{
using (var container = new UnityAutoMoqContainer())
{
//(SNIP)
var serviceMock = container.GetMock<ITreatmentPlanService>();
var resetEvent = new ManualResetEventSlim();
serviceMock.Setup(x=>x.GetSinglePatientViewTable(dateWindow, currentPatient, false))
.Returns(() =>
{
resetEvent.Set();
return new ObservableCollection<SinglePatientViewDataRow>();
});
var viewModel = container.Resolve<SinglePatientViewModel>();
//(SNIP)
viewModel.PatientsHadTPClosed(guids, Guid.NewGuid());
waited = resetEvent.Wait(timeout);
if(!waited)
Assert.Fail("GetSinglePatientViewTable was not called within the timeout of {0} ms", timeout);
//(SNIP)
serviceMock.Verify(x => x.GetSinglePatientViewTable(dateWindow, currentPatient, false), Times.Once);
}
}
If this approach works or not for you all depends on what your unit test is actually testing. Because you check Assert.IsTrue(vm.Jobs.Count > 0) it looks like you have extra logic that is being done after the await GetData(...); call, so this might not be applicable for your current problem. However, this may be helpful for other unit tests you need to write for your view model.
I have a silverlight application which is making multiple async calls:
The problem I am facing is to how to determine if all the async calls are finished so that I can stop displaying the progress indicator. In the example below, progress indicator is stopped as soon as the first async method returns.
Any tips on how to resolve this ?
Constructor()
{
startprogressindicator();
callasync1(finished1);
callasync2(finished2);
//.... and so on
}
public void finished1()
{
stopprogressindicator();
}
public void finished2()
{
stopprogressindicator();
}
You need to asynchronously wait for both methods to finish, currently you call stopprogressindicator as soon as any of the method completes.
Refactor your code to return Task from callasync1 and callasync2 Then you can do
var task1 = callasync1();
var task2 = callasync2();
Task.Factory.ContinueWhenAll(new []{task1, task2}, (antecedents) => stopprogressindicator());
I do like the idea of using Task API, but in this case you may simply use a counter:
int _asyncCalls = 0;
Constructor()
{
startprogressindicator();
Interlocked.Increment(ref _asyncCalls);
try
{
// better yet, do Interlocked.Increment(ref _asyncCalls) inside
// each callasyncN
Interlocked.Increment(ref _asyncCalls);
callasync1(finished1);
Interlocked.Increment(ref _asyncCalls);
callasync2(finished2);
//.... and so on
}
finally
{
checkStopProgreessIndicator();
}
}
public checkStopProgreessIndicator()
{
if (Interlocked.Decrement(ref _asyncCalls) == 0)
stopprogressindicator();
}
public void finished1()
{
checkStopProgreessIndicator()
}
public void finished2()
{
checkStopProgreessIndicator()
}
I am trying to block RequestHandler.ParseAll() with await ConsumerTask;, but when i set a breakpoint there, i ALWAYS get the "Done..." output first... and then Parse2() fails with a NullReferenceException. (thats my guess: "the GC starts cleaning up because _handler got out of scope")
Anyway, I can't figure out why that happens.
class MainClass
{
public async void DoWork()
{
RequestHandler _handler = new RequestHandler();
string[] mUrls;
/* fill mUrls here with values */
await Task.Run(() => _handler.ParseSpecific(mUrls));
Console.WriteLine("Done...");
}
}
static class Parser
{
public static async Task<IEnumerable<string>> QueryWebPage(string url) { /*Query the url*/ }
public static async Task Parse1(Query query)
{
Parallel.ForEach(/*Process data here*/);
}
public static async Task Parse2(Query query)
{
foreach(string line in query.WebPage)
/* Here i get a NullReference exception because query.WebPage == null */
}
}
sealed class RequestHandler
{
private BlockingCollection<Query> Queue;
private Task ConsumerTask = Task.Run(() => /* call consume() for each elem in the queue*/);
private async void Consume(Query obj)
{
await (obj.BoolField ? Parser.Parse1(obj) : Parser.Parse2(obj));
}
public async void ParseSpecific(string[] urls)
{
foreach(string v in urls)
Queue.Add(new Query(await QueryWebPage(v), BoolField: false));
Queue.CompleteAdding();
await ConsumerTask;
await ParseAll(true);
}
private async Task ParseAll(bool onlySome)
{
ReInit();
Parallel.ForEach(mCollection, v => Queue.Add(new Query(url, BoolField:false)));
Queue.CompleteAdding();
await ConsumerTask;
/* Process stuff further */
}
}
struct Query
{
public readonly string[] WebPage;
public readonly bool BoolField;
public Query(uint e, IEnumerable<string> page, bool b) : this()
{
Webpage = page.ToArray();
BoolField = b;
}
}
CodesInChaos has spotted the problem in comments. It stems from having async methods returning void, which you should almost never do - it means you've got no way to track them.
Instead, if your async methods don't have any actual value to return, you should just make them return Task.
What's happening is that ParseSpecific is only running synchronously until the first await QueryWebPage(v) that doesn't complete immediately. It's then returning... so the task started here:
await Task.Run(() => _handler.ParseSpecific(mUrls));
... completes immediately, and "Done" gets printed.
Once you've made all your async methods return Task, you can await them. You also won't need Task.Run at all. So you'd have:
public async void DoWork()
{
RequestHandler _handler = new RequestHandler();
string[] mUrls;
await _handler.ParseSpecific(mUrls);
Console.WriteLine("Done...");
}
...
public async TaskParseSpecific(string[] urls)
{
foreach(string v in urls)
{
// Refactored for readability, although I'm not sure it really
// makes sense now that it's clearer! Are you sure this is what
// you want?
var page = await QueryWebPage(v);
Queue.Add(new Query(page, false);
}
Queue.CompleteAdding();
await ConsumerTask;
await ParseAll(true);
}
Your Reinit method also needs changing, as currently the ConsumerTask will basically complete almost immediately, as Consume will return immediately as it's another async method returning void.
To be honest, what you've got looks very complex, without a proper understanding of async/await. I would read up more on async/await and then probably start from scratch. I strongly suspect you can make this much, much simpler. You might also want to read up on TPL Dataflow which is designed to make producer/consumer scenarios simpler.