How can you mock an Automatonymous Activity? - c#

I'm trying to unit test my state machine and that it transitions to the states I expect when certain events are received. To do this I'm using the InMemoryTestHarness which enables me to create my state machine and then using the bus I can publish messages to it.
For the most part this is fine. However, there are a couple of examples where it then goes off and does an activity before it transitions state like this:
During(Submitted,
When(OnFlyCoolThingRequest)
.Activity(activitySelector => activitySelector.OfType<MakeItFlyActivity>()).TransitionTo(Flying));
At this point I don't want to test the activity and as it happens it appears that it seems to actually break the test (just hangs). My test is currently setup like this:
[Test]
public async Task CoolThingSagaShouldTransitionToFlyingStateOnFlyCoolThingRequestEvent()
{
var stateMachine = new CoolStateMachine();
var harness = new InMemoryTestHarness();
var saga = harness.StateMachineSaga<CoolThingSaga, CoolStateMachine>(new CoolStateMachine());
await harness.Start();
try
{
var coolThing = GetCoolThing();
// Create a CoolThing instance.
await harness.Bus.Publish(new CoolThingCreated(coolThing.Id));
var instanceIdSubmitted = await saga.Exists(coolThing.Id, state => state.Submitted);
// Publish a FlyCoolThingRequest event for the above instance.
await harness.Bus.Publish(new FlyCoolThingRequest(coolThing.Id, DateTime.UtcNow));
var instanceIdFlying = await saga.Exists(coolThing.Id, state => state.Flying);
Assert.That(instanceIdSubmitted.Value, Is.EqualTo(instanceIdFlying.Value), $"Instance id returned between the state of '{stateMachine.Submitted.Name}' and '{stateMachine.Flying.Name}' should be the same.");
Assert.That(instanceIdFlying, Is.Not.Null, $"A CoolThingSaga instance should have been created with Id {instanceIdFlying} and a be in the state of '{stateMachine.Flying}'.");
}
finally
{
await harness.Stop();
}
}
At the moment this type of test works for testing states that don't have activities associated with them. However, the tests seem to hang when an activity is involved. So how can I mock the activity so it doesn't actually try and perform that logic?
UPDATE
Looks like it fails to create the activity due to not being able to find a parameterless constructor. My activity is akin to this:
public sealed class MakeItFlyActivity : Activity<CoolThingSaga, FlyCoolThingRequest>
{
private readonly FlyingService _flyingService;
public MakeItFlyActivity(FlyingService flyingService)
{
_flyingService = flyingService;
}
public async Task Execute(BehaviorContext<CoolThingSaga, FlyCoolThingRequest> context, Behavior<CoolThingSaga, FlyCoolThingRequest> next)
{
await flyingService.MakeItFly(context.Instance.Details.Id);
await next.Execute(context);
}
public void Accept(StateMachineVisitor visitor) => visitor.Visit(this);
public async Task Faulted<TException>(BehaviorExceptionContext<CoolThingSaga, FlyCoolThingRequest, TException> context, Behavior<CoolThingSaga, FlyCoolThingRequest> next)
where TException : Exception => await next.Faulted(context);
public void Probe(ProbeContext context) => context.CreateScope(nameof(MakeItFlyActivity));
}
My setup is as follows:
private InMemoryTestHarness fHarness;
private StateMachineSagaTestHarness<CoolThingSaga, CoolStateMachine> fSaga;
private CoolStateMachine fStateMachine;
[OneTimeSetUp]
public async Task Setup()
{
var provider = new ServiceCollection()
.AddSingleton<ILoggerFactory>(p => new TestOutputLoggerFactory(true))
.AddMassTransitInMemoryTestHarness(cfg =>
{
cfg.AddSagaStateMachine<CoolStateMachine, CoolThingSaga>().InMemoryRepository();
cfg.AddSagaStateMachineTestHarness<CoolStateMachine, CoolThingSaga>();
})
.AddSingleton(p => new FlyingService(TimeSpan.FromMinutes(15), NullLogger<FlyingService>.Instance))
.AddScoped<MakeItFlyActivity>()
.BuildServiceProvider(true);
fHarness = provider.GetRequiredService<InMemoryTestHarness>();
fSaga = fHarness.StateMachineSaga<CoolThingSaga, CoolStateMachine>(new CoolStateMachine());
fStateMachine = provider.GetRequiredService<CoolStateMachine>();
await fHarness.Start();
}

To test state machines, you should be using the container based test harness as outlined in the documentation. This will ensure that activities and their dependencies can be resolved at runtime. Your test likely hangs because the activity could not be created.
The documentation for v8 is linked, you can also refer to the v7 documentation if you're using an earlier version.

Related

Unit test case for SaveChangesAsync

I am using Mass Transit, Entity Framework, C# in my project.
I have my consumer consuming an event and which insert data in to the table. I would like to know how to mock the consumer and unit test case for this method.
public async Task Consume(ConsumeContext<MyEvent> context)
{
private readonly MyDbContext _dbContext;
try
{
// Here logic to insert record in to new database
var data = new MyService.TableNmae()
{
Id = context.Message.MyId,
Description = "test data"
};
_ = _dbContext.TableName.AddAsync(data);
_ = _dbContext.SaveChangesAsync(context.CancellationToken);
}
catch (Exception ex)
{
_logger.LogCritical($"{GetType().Name}:{nameof(Consume)} {ex}");
}
await Task.CompletedTask;
}
Here is my unit test case code i have added
{
private ITestHarness _testHarness;
[SetUp]
public void Initialize()
{
var serviceCollection = new ServiceCollection();
serviceCollection.AddMassTransitTestHarness(busRegistrationConfigurator =>
{
busRegistrationConfigurator.AddConsumer<MyConsumer>();
});
var serviceProvider = serviceCollection.BuildServiceProvider();
_testHarness = serviceProvider.GetRequiredService<ITestHarness>();
}
[Test]
public async Task TestMethod1()
{
await _testHarness.Start();
await _testHarness.Bus.Publish(new MyEvent { Code = "H"});
Assert.That(await _testHarness.Published.Any<MyEvent>(), Is.True);
Assert.That(await _testHarness.Consumed.Any<MyEvent>(), Is.True);
}
}
Expected: True
But was: False.
Here the first assert is true but second assert always false,
Instead of trying to mock ConsumeContext<T>, I recommend using the MassTransit test harness, which is documented on the web site.
As others have pointed out, testing simply that your entity was saved is a pretty trivial test, and more of an integration test assuming you are verifying that the entity was saving all required properties, etc. Not something I would test in isolation, vs. ensuring the data is available for higher level operations being tested.

Why is the WebApplicationFactory not resetting between tests?

I have two tests of an ASP.NET core webapp within a single test class. The class uses an IClassFixture<WebApplicationFactory<Startup>>.
The first test is a happy path test: when a message arrives on a Kafka topic, some file appears in a particular output directory.
The second test is to verify that if something goes wrong with outputting said file, the webapp should then be in an unhealthy status.
For brevity, I'm omitting the code that publishes to Kafka and that generates the output folder the application saves files to - neither of these are relevant.
[CollectionDefinition("e2e", DisableParallelization = true)]
[Trait("Category", "IntegrationTest")]
public class EndToEndTest : IClassFixture<WebApplicationFactory<Startup>>, IClassFixture<KafkaFixture>
{
private readonly WebApplicationFactory<Startup> _factory;
public EndToEndTest(WebApplicationFactory<Startup> factory)
{
_factory = factory;
KafkaUtils.Publish(SomeMessage()).GetAwaiter().GetResult();
}
[Fact]
public void WhenXReceived_ThenFileIsOutput()
{
var options = OutputFolder();
Directory.CreateDirectory(options.Path);
RunService(services => services.AddSingleton(Options.Create(options))).CreateClient();
Thread.Sleep(10_000);
var outputDirectory = Directory.GetFiles(options.Path);
Assert.Single(outputDirectory);
var file = outputDirectory.Single();
Assert.NotEmpty(File.ReadAllLinesAsync(file).GetAwaiter().GetResult());
Directory.Delete(options.Path, true);
}
[Fact]
public void WhenFileTransferFails_ThenAppShouldBeUnhealthy()
{
var options = OutputFolder();
// only try once, so we don't have to wait
var retry = new RetryPolicyConfiguration {OnErrorRetryCount = 1};
var factory = RunService(services =>
{
services.AddSingleton(Options.Create(options));
services.AddSingleton(Options.Create(retry));
services.RemoveAll<IFileWriter>();
services.AddScoped<IFileWriter, ThrowingFileWriter>();
});
var client = factory.CreateClient();
Thread.Sleep(10_000);
Assert.False(Directory.Exists(options.Path));
var response = client.GetAsync("/health/ready").GetAwaiter().GetResult();
var stream = response.Content.ReadAsStreamAsync().GetAwaiter().GetResult();
using var reader = new StreamReader(stream);
var body = reader.ReadToEndAsync().GetAwaiter().GetResult();
var health = JsonConvert.DeserializeObject<HealthCheckResult>(body);
Assert.NotEqual(HealthStatus.Healthy, health.Status);
}
protected WebApplicationFactory<Startup> RunService(Action<IServiceCollection> serviceConfig)
{
return _factory
.WithWebHostBuilder(builder => builder
.ConfigureTestServices(services =>
{
serviceConfig(services);
services.AddSubscribeBus(new ConfigurationBuilder()
.AddJsonFile(ConfigMapFileProvider.FromRelativePath("config"),
"appsettings.e2e.json", true, true)
.Build());
}));
}
}
public class ThrowingFileWriter : IFileWriter
{
public void Write(string fileName, envelope envelope)
{
throw new Exception("foo");
}
public void Delete(string fileName)
{
}
}
If I run WhenFileTransferFails_ThenAppShouldBeUnhealthy() (unhealthyTest from here on, for brevity) on its own, then the test passes. But if I run the entire test class, then WhenXReceived_ThenFileIsOutput() (healthyTest, for brevity) runs before it, and for some reason unhealthyTest seems to use the service context that was created for healthyTest.
I've tried my best to work out what's going on, but it doesn't make sense to me. When I debug through it, the order of events is roughly as follows:
healthyTest starts
Startup invoked for healthytest
Constructor of service that uses IFileWriter is invoked with real instance of service
healthyTest completes
unhealthyTest starts
Constructor of service that uses IFileWriter is invoked with real instance of service (???)
Startup invoked for unhealthyTest
serviceConfig action invoked for unhealthyTest: all implementations of IFileWriter replaced with ThrowingFileWriter - but this is too late, it's already been constructed with the real instance in step 6
Consequently, the real service is used instead of the throwing service, and the exception handler that sets the app status to Unhealthy is never invoked.
Originally this was all running asynchronously, but I thought that perhaps the async nature of the tests meant that the two were conflicting with the webapp created by the factory - hence all the GetAwaiter().GetResult().
What am I doing wrong in my setup of the webapp for the test?
Note: it's unfortunately absolutely not an option to move these tests into their own test classes.

Issue when upgrading Autofac with async tasks and owned instances

I have an issue with Autofac after upgrading from 4.9.2 to 5.2 in my ASP.NET MVC application.
I make use of Func<Owned<T>> factory pattern in the Controller because an Controller Action starts a Long running Task and will run longer than the request exists. In that Task I am resolving other instances.
This worked fine in Autofac 4.9.2. But after upgrading to Autofac 5.2 the parent Lifetime scope (AutofacWebRequest) gets disposed and it not possible to resolve instances within the owned instance anymore.
Instances cannot be resolved and nested lifetimes cannot be created from this LifetimeScope as it (or one of its parent scopes) has already been disposed.
Is there something I can do to work around this or is there a best practice?
Controller Code:
private readonly Func<Owned<IBusinessLogic>> _businessLogicFactory;
public ActionResult Index()
{
var businessLogic = _businessLogicFactory();
var unitOfWorkFactory = _unitOfWorkFactory;
Task.Run(() =>
{
System.Threading.Thread.Sleep(5000); // Sleep simulates that it may take some time until other instances are resolved
using (businessLogic)
{
var task = businessLogic.Value.DoHardBusinessAsync();
task.Wait();
}
});
return View();
}
Business Logic Code (also using a factory):
public class BusinessLogic : IBusinessLogic
{
private readonly Func<Owned<OtherBusinessLogic>> _otherBusinessLogicFactory;
public BusinessLogic(Func<Owned<OtherBusinessLogic>> otherBusinessLogicFactory)
{
_otherBusinessLogicFactory = otherBusinessLogicFactory;
}
public async Task DoHardBusinessAsync()
{
using (var otherBusiness = _otherBusinessLogicFactory())
{
await otherBusiness.Value.DoHardBusinessAsync();
}
}
}
You could try to create a new lifetime scope that is independent of the request scope to be used with your long running task like so
Task.Run(() =>
{
using (var scope = container.BeginLifetimeScope())
{
System.Threading.Thread.Sleep(5000); // Sleep simulates that it may take some time until other instances are resolved
using (businessLogic)
{
var task = businessLogic.Value.DoHardBusinessAsync();
task.Wait();
}
}
});
Look at this question for ideas on how to get a hold of the container
Retrieving Autofac container to resolve services
#NataliaMuray's approach is awesome - one downside of it is that it tends to encourage Service Locator style resolving rather than constructor injection. This can tend to "hide" dependencies, making it harder to identify the dependencies of a given class.
One potential solution is to introduce the notion of a dependency that is explicit that it wraps another dependency that you want to resolve outside the normal web request's lifetime scope.
The code might look something like:
public class AsyncRunner : IAsyncRunner
{
public ExecutionResult TryExecute<TService>(Action<TService> toEvaluate, string #exceptionErrorMessage, int timeoutMilliseconds, string additionalErrorInformation = "")
{
try
{
var task = new Task(() =>
{
using (var scope = container.BeginLifetimeScope())
{
var service = scope.Resolve<TService>();
toEvaluate(service);
}
});
task.ContinueWith(t => { /* logging here */, TaskContinuationOptions.OnlyOnFaulted | TaskContinuationOptions.ExecuteSynchronously).SuppressExceptions();
task.Start();
var completedWithinTime = task.Wait(timeoutMilliseconds);
return completedWithinTime ? ExecutionResult.Ok : ExecutionResult.TimedOut;
}
catch (Exception e)
{
/* logging here */
return ExecutionResult.ThrewException;
}
}
}
Register IAsyncRunner with Autofac as well.
And then your dependency, instead of
private readonly Func<Owned<IBusinessLogic>> _businessLogicFactory;
would be
private readonly IAsyncRunner<IBusinessLogic>> _businessLogic;
And instead of:
var businessLogic = _businessLogicFactory();
var unitOfWorkFactory = _unitOfWorkFactory;
Task.Run(() =>
{
System.Threading.Thread.Sleep(5000); // Sleep simulates that it may take some time until other instances are resolved
using (businessLogic)
{
var task = businessLogic.Value.DoHardBusinessAsync();
task.Wait();
}
});
would be:
//var businessLogic = _businessLogicFactory();
var unitOfWorkFactory = _unitOfWorkFactory;
Task.Run(() =>
{
System.Threading.Thread.Sleep(5000); // Sleep simulates that it may take some time until other instances are resolved
_businessLogic.TryExecute(z => {
var task = z.Value.DoHardBusinessAsync();
task.Wait();
});
});
The advantage of this style is that the property and constructor injection makes clear what the dependencies are, and how they are being used (i.e. the declaration makes clear that it will be resolved outside the context of the standard lifetime scope). Note you don't need to use Owned with my suggestion (disposal of the lifetime scope that is manually constructed will be sufficient). I have removed the use of Func, but you could use Func or Lazy if you really needed it alongside my suggestion.

How can I write unit test for my background service?

I'm working with the HostBuilder in .NET Core (not the WebHost !).
I have one Hosted Service running in my application that overrides the ExecuteAsync/StopAsync methods of the background Service and I want to unit test it.
Here is my HostedService:
public class DeviceToCloudMessageHostedService : BackgroundService
{
private readonly IDeviceToCloudMessageService _deviceToCloudMessageService;
private readonly AppConfig _appConfig;
public DeviceToCloudMessageHostedService(IDeviceToCloudMessageService deviceToCloudMessageService, IOptionsMonitor<AppConfig> appConfig)
{
_deviceToCloudMessageService = deviceToCloudMessageService;
_appConfig = appConfig.CurrentValue;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
await _deviceToCloudMessageService.DoStuff(stoppingToken);
await Task.Delay(_appConfig.Parameter1, stoppingToken);
}
}
public override Task StopAsync(CancellationToken cancellationToken)
{
Log.Information("Task Cancelled");
_deviceToCloudMessageService.EndStuff();
return base.StopAsync(cancellationToken);
}
I already found this post: Integration Test for Hosted Service in .NET Core
But it's explained for a QueuedBackgroundService and I don't really know if I can test mine the same way.
I just want to know if my code is executed. I don't want any specific result.
Do you have any idea of how I can test it?
You should still be able to follow a similar format as the linked answer.
Mock the dependencies and inject them, invoke the methods under test and assert the expected behavior.
The following uses Moq to mock the dependencies along with ServiceCollection to do the heavy lifting of injecting the dependencies.
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
[TestMethod]
public async Task DeviceToCloudMessageHostedService_Should_DoStuff() {
//Arrange
IServiceCollection services = new ServiceCollection();
services.AddSingleton<IHostedService, DeviceToCloudMessageHostedService>();
//mock the dependencies for injection
services.AddSingleton(Mock.Of<IDeviceToCloudMessageService>(_ =>
_.DoStuff(It.IsAny<CancellationToken>()) == Task.CompletedTask
));
services.AddSingleton(Mock.Of<IOptionsMonitor<AppConfig>>(_ =>
_.CurrentValue == Mock.Of<AppConfig>(c =>
c.Parameter1 == TimeSpan.FromMilliseconds(1000)
)
));
var serviceProvider = services.BuildServiceProvider();
var hostedService = serviceProvider.GetService<IHostedService>();
//Act
await hostedService.StartAsync(CancellationToken.None);
await Task.Delay(1000);//Give some time to invoke the methods under test
await hostedService.StopAsync(CancellationToken.None);
//Assert
var deviceToCloudMessageService = serviceProvider
.GetRequiredService<IDeviceToCloudMessageService>();
//extracting mock to do verifications
var mock = Mock.Get(deviceToCloudMessageService);
//assert expected behavior
mock.Verify(_ => _.DoStuff(It.IsAny<CancellationToken>()), Times.AtLeastOnce);
mock.Verify(_ => _.EndStuff(), Times.AtLeastOnce());
}
Now, ideally this would count as testing framework code since you are basically testing that a BackgroundService behaves as expected when run, but it should demonstrate enough about how one would test such a service in isolation
Another example based on #Nkosi's excellent answer. For I was testing this StartupBackgroundService, which has a protected method ExecuteAsync:
public class StartupBackgroundService : BackgroundService
{
private readonly StartupHealthCheck _healthCheck;
public StartupBackgroundService(StartupHealthCheck healthCheck)
=> _healthCheck = healthCheck;
protected override Task ExecuteAsync(CancellationToken stoppingToken)
{
_healthCheck.StartupCompleted = true;
return Task.CompletedTask;
}
}
I can't change the scope from protected to internal and expose it with [assembly: InternalsVisibleTo("TestsAssembly")] because its derived from an Abstract class.
So I came up with this magic, it calls ExecuteAsync not StartAsync:
[Test]
public async Task Should_Setup_StartupBackgroundService()
{
//Arrange
var startUpBackServ = new StartupBackgroundService(new Base.HealthCheck.StartupHealthCheck());
// Act
startUpBackServ.StartAsync(It.IsAny<CancellationToken>()); // It calls ExecuteAsync magically!
//Assert
}
It's MAGIC!
Here's the StartupHealthCheck:
public class StartupHealthCheck : IHealthCheck
{
public bool StartupCompleted { get; set; }
public Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context = null, CancellationToken cancellationToken = default)
{
if (StartupCompleted)
{
return Task.FromResult(HealthCheckResult.Healthy("The startup task has completed."));
}
return Task.FromResult(HealthCheckResult.Unhealthy("That startup task is still running."));
}
}

How to execute multiple parallel tasks on completion of a prior task

I have a situation where I need to call a web service and, on successful completion, do multiple things with the results returned from the web service. I have developed code that "works" -- just not as I intended. Specifically, I want to take the results from the call to the web service and pass those results onto multiple successive tasks that are to execute in parallel, but what I have at the moment executes the first successive task before starting the second.
I've put together a much simplified example of what I'm currently doing that'll hopefully help illustrate this situation. First, the implementation:
public interface IConfigurationSettings
{
int? ConfigurationSetting { get; set; }
}
public interface IPrintCommandHandler
{
System.Threading.Tasks.Task<bool> ExecuteAsync(byte[] reportContent);
}
public interface ISaveCommandHandler
{
System.Threading.Tasks.Task<bool> ExecuteAsync(byte[] reportContent);
}
public interface IWebService
{
System.Threading.Tasks.Task<object> RetrieveReportAsync(string searchToken, string reportFormat);
}
public class ReportCommandHandler
{
private readonly IConfigurationSettings _configurationSettings;
private readonly IPrintCommandHandler _printCommandHandler;
private readonly ISaveCommandHandler _saveCommandHandler;
private readonly IWebService _webService;
public ReportCommandHandler(IWebService webService, IPrintCommandHandler printCommandHandler, ISaveCommandHandler saveCommandHandler, IConfigurationSettings configurationSettings)
{
_webService = webService;
_printCommandHandler = printCommandHandler;
_saveCommandHandler = saveCommandHandler;
_configurationSettings = configurationSettings;
}
public async Task<bool> ExecuteAsync(string searchToken)
{
var reportTask = _webService.RetrieveReportAsync(searchToken, "PDF");
var nextStepTasks = new List<Task<bool>>();
// Run "print" task after report task.
var printTask = await reportTask.ContinueWith(task => _printCommandHandler.ExecuteAsync((byte[]) task.Result));
nextStepTasks.Add(printTask);
// Run "save" task after report task.
if (_configurationSettings.ConfigurationSetting.HasValue)
{
var saveTask = await reportTask.ContinueWith(task => _saveCommandHandler.ExecuteAsync((byte[]) task.Result));
nextStepTasks.Add(saveTask);
}
var reportTaskResult = await Task.WhenAll(nextStepTasks);
return reportTaskResult.Aggregate(true, (current, result) => current & result);
}
}
So, the web service (third party, nothing to do with me) has an endpoint for doing a search/lookup that, if successful, returns a reference number (I've called it a search token in my example). This reference number is then used to retrieve the results of the lookup (using a different endpoint) in any of several different formats.
The IWebService interface in this example is representative of an application service I created to manage interaction with the web service. The actual implementation has other methods on it for doing a lookup, ping, etc.
Just to make things more interesting, one of the successive tasks is required (will always execute after the primary task) but the other successive task is optional, execution subject to a configuration setting set elsewhere in the application.
To more easily demonstrate the issue, I created a unit test:
public class RhinoMockRepository : IDisposable
{
private readonly ArrayList _mockObjectRepository;
public RhinoMockRepository()
{
_mockObjectRepository = new ArrayList();
}
public T CreateMock<T>() where T : class
{
var mock = MockRepository.GenerateMock<T>();
_mockObjectRepository.Add(mock);
return mock;
}
public T CreateStub<T>() where T : class
{
return MockRepository.GenerateStub<T>();
}
public void Dispose()
{
foreach (var obj in _mockObjectRepository) obj.VerifyAllExpectations();
_mockObjectRepository.Clear();
}
}
[TestFixture]
public class TapTest
{
private const string SearchToken = "F71C8B50-ECD1-4C02-AD3F-6C24F1AF3D9A";
[Test]
public void ReportCommandExecutesPrintAndSave()
{
using (var repository = new RhinoMockRepository())
{
// Arrange
const string reportContent = "This is a PDF file.";
var reportContentBytes = System.Text.Encoding.Default.GetBytes(reportContent);
var retrieveReportResult = System.Threading.Tasks.Task.FromResult<object>(reportContentBytes);
var webServiceMock = repository.CreateMock<IWebService>();
webServiceMock.Stub(x => x.RetrieveReportAsync(SearchToken, "PDF")).Return(retrieveReportResult);
var printCommandHandlerMock = repository.CreateMock<IPrintCommandHandler>();
var printResult = System.Threading.Tasks.Task.FromResult(true);
printCommandHandlerMock
.Expect(x => x.ExecuteAsync(reportContentBytes))
//.WhenCalled(method => System.Threading.Thread.Sleep(TimeSpan.FromSeconds(2)))
.Return(printResult);
var configurationSettingsStub = repository.CreateStub<IConfigurationSettings>();
configurationSettingsStub.ConfigurationSetting = 10;
var saveCommandHandlerMock = repository.CreateMock<ISaveCommandHandler>();
var saveResult = System.Threading.Tasks.Task.FromResult(true);
saveCommandHandlerMock.Expect(x => x.ExecuteAsync(reportContentBytes)).Return(saveResult);
// Act
var reportCommandHandler = new ReportCommandHandler(webServiceMock, printCommandHandlerMock, saveCommandHandlerMock, configurationSettingsStub);
var result = System.Threading.Tasks.Task
.Run(async () => await reportCommandHandler.ExecuteAsync(SearchToken))
.Result;
// Assert
Assert.That(result, Is.True);
}
}
}
Ideally, on completion of the call to RetrieveReportAsync() on IWebService both the "print" and "save" command handlers should be executed simultaneously, having received a copy of the results from RetrieveReportAsync(). However, if the call to WhenCalled... in the unit test is uncommented, and on stepping through the implementation of ReportCommandHandler.ExecuteAsync(), you can see that the "print" command executes and completes before it gets to the "save" command. Now, I am aware that the whole point of await is to suspend execution of the calling async method until the awaited code completes, but it isn't clear to me how to instantiate both the "print" and "save" commands (tasks) as continuations of the "report" task such that they both execute in parallel when the "report" task completes, and the "report" command is then able to return a result that is based on the results from both the "print" and "save" commands (tasks).
Your question really involves addressing two different goals:
How to wait for a task?
How to execute two other tasks concurrently?
I find the mixing of await and ContinueWith() in your code confusing. It's not clear to me why you did that. One of the key things await does for you is to automatically set up a continuation, so you don't have to call ContinueWith() explicitly. Yet, you do anyway.
On the assumption that's simply a mistake, out of lack of full understanding of how to accomplish your goal, here's how I'd have written your method:
public async Task<bool> ExecuteAsync(string searchToken)
{
var reportTaskResult = await _webService.RetrieveReportAsync(searchToken, "PDF");
var nextStepTasks = new List<Task<bool>>();
// Run "print" task after report task.
var printTask = _printCommandHandler.ExecuteAsync((byte[]) reportTaskResult);
nextStepTasks.Add(printTask);
// Run "save" task after report task.
if (_configurationSettings.ConfigurationSetting.HasValue)
{
var saveTask = _saveCommandHandler.ExecuteAsync((byte[]) reportTaskResult);
nextStepTasks.Add(saveTask);
}
var reportTaskResult = await Task.WhenAll(nextStepTasks);
return reportTaskResult.Aggregate(false, (current, result) => current | result);
}
In other words, do await the original task first. Then you know it's done and have its result. At that time, go ahead and start the other tasks, adding their Task object to your list, but not awaiting each one individually. Finally, await the entire list of tasks.

Categories