Course contentsShow
← C#
Lesson 1634 of 1,90815. Software Quality and TestingFree lesson

Simulating Asynchronous Service Responses

Return completed or faulted tasks from mocked asynchronous service methods.

In this lesson, you will learn how to configure your mocks to return Task objects that are pre-completed or intentionally faulted to simulate various asynchronous service outcomes.

The Problem with Real Async Calls

When unit testing, we aim to isolate the system under test (SUT) from external dependencies. If your service relies on an IExternalService that performs network I/O, calling the real implementation in a test is slow and unreliable. While you could use Task.Run inside a mock, it introduces non-determinism and race conditions. Instead, we use built-in helper methods to return tasks in a specific state immediately.

Returning Completed Tasks

To simulate a successful asynchronous operation without actually waiting for a thread, we use Task.FromResult() for methods returning Task<T>, or Task.CompletedTask for methods returning a simple Task.

Using a library like Moq, you configure your mock like this:

var mockService = new Mock<IOrderService>();

// Simulating a successful result
mockService.Setup(s => s.GetOrderStatusAsync(123))
           .ReturnsAsync("Shipped"); // Helper for Task.FromResult

// Simulating a void-returning async method
mockService.Setup(s => s.ProcessNotificationAsync())
           .Returns(Task.CompletedTask);

Simulating Faulted Tasks

In real-world scenarios, network calls fail. To test your error-handling logic (like try-catch blocks or resilience policies), you must be able to force your mock to throw an exception asynchronously.

You do this by returning a Task that is already in a Faulted state. Using Moq, you can achieve this cleanly:

mockService.Setup(s => s.GetOrderStatusAsync(It.IsAny<int>()))
           .Returns(Task.FromException<string>(new HttpRequestException("Service unreachable")));

By returning a Task.FromException, the calling code will treat the returned task as if it failed during execution. When your SUT awaits this task, the exception will be thrown exactly as if the service had genuinely failed, allowing you to verify that your error-handling logic triggers as expected. This approach ensures your tests remain fast, deterministic, and isolated from infrastructure concerns.

Key Takeaway: Use Task.CompletedTask, Task.FromResult, and Task.FromException to force your asynchronous mocks into a predictable state, ensuring your tests cover both success and failure paths without requiring real I/O.