public async Task UpdateShouldSetAndResetLoadingFlagOfAffectedOptions() { // Arrange var tcs = new TaskCompletionSource <int>(); var dispatcher = CreateDispatcher(); var updateFinishBlock = new ManualResetEventSlim(false); async Task <IValueCalculationResult <int> > CalculationTaskFactory(CancellationToken ct) { await Task.Delay(50, ct); return(ValueCalculationResult.Success(123)); } void HandleResultCallback(int arg) { tcs.SetResult(arg); } using var sut = new AsyncValueCalculator <int>(dispatcher, CalculationTaskFactory, HandleResultCallback); var option = Substitute.For <IValueOption>(); sut.Affect(option); option.When(o => o.IsLoading = false) .Do(x => updateFinishBlock.Set()); // Act sut.Update(); // Assert await tcs.Task; updateFinishBlock.Wait(TimeSpan.FromSeconds(1)); Assert.False(option.IsLoading); option.Received(1).IsLoading = true; option.Received(1).IsLoading = false; }
public async Task UpdateShouldNotThrowWhenTaskThrows() { // Arrange var tcs = new TaskCompletionSource <int>(); var dispatcher = CreateDispatcher(); Task <IValueCalculationResult <int> > CalculationTaskFactory(CancellationToken ct) { Task.Run(() => { Thread.Sleep(15); tcs.SetResult(0); }, ct); return(Task.FromException <IValueCalculationResult <int> >(new Exception("Test"))); } void HandleResultCallback(int arg) { tcs.SetResult(arg); } using var sut = new AsyncValueCalculator <int>(dispatcher, CalculationTaskFactory, HandleResultCallback); var option = Substitute.For <IValueOption>(); sut.Affect(option); // Act sut.Update(); // Assert var result = await tcs.Task; option.Received(1).IsLoading = true; option.Received(2).IsLoading = false; Assert.Equal(0, result); }
public async Task MultipleUpdateShouldTriggerCallbackOnlyOnce() { // As with all async code there is much magic happening here: // We want to start a second update while the first one is still running. // To do so we start the first update wait for a WaitHandle to be // signaled and then start the second update. // // When the update method is run, it will signal the wait handle // then wait some time to let the second update start // // The callback will set a TaskCompletionSource and this task is awaited // in the test. // // The asserts check that // - The update method was called twice // - The result callback was only called once // - The affected option is not loading anymore // - The loading flags was set and unset correctly // // Arrange var tcs = new TaskCompletionSource <int>(); var dispatcher = CreateDispatcher(); var callbackCount = 0; var calculationCount = 0; var updateStartBlock = new ManualResetEventSlim(false); var updateFinishBlock = new ManualResetEventSlim(false); async Task <IValueCalculationResult <int> > CalculationTaskFactory(CancellationToken ct) { Interlocked.Increment(ref calculationCount); updateStartBlock.Set(); await Task.Delay(25, ct); return(ValueCalculationResult.Success(123)); } void HandleResultCallback(int arg) { Interlocked.Increment(ref callbackCount); tcs.SetResult(arg); } using var sut = new AsyncValueCalculator <int>(dispatcher, CalculationTaskFactory, HandleResultCallback); var option = Substitute.For <IValueOption>(); sut.Affect(option); option.When(o => o.IsLoading = false) .Do(x => updateFinishBlock.Set()); // Act sut.Update(); updateStartBlock.Wait(); sut.Update(); // Assert await tcs.Task; updateFinishBlock.Wait(TimeSpan.FromSeconds(1)); Assert.Equal(1, callbackCount); Assert.Equal(2, calculationCount); Assert.False(option.IsLoading); option.Received(2).IsLoading = true; option.Received(1).IsLoading = false; }