Updated on

To mock an async method with Moq, set it up with ReturnsAsync() instead of Returns(). For a method returning Task<T>, ReturnsAsync(value) hands back a completed task wrapping that value, so the code under test can await it normally.

For a method returning a plain Task with no result there is nothing to wrap, so the setup uses Returns(Task.CompletedTask). Those two lines cover nearly every async mock we will ever write, and the rest of this article shows both against a real repository.

To download the source code for this article, you can visit our GitHub repository.

How Do We Use ReturnsAsync in Moq?

ReturnsAsync() is the Moq setup method for any mocked method whose return type is Task<T>. It takes the value we want the caller to receive, and Moq wraps it in an already-completed task.

var mock = new Mock<IArticleRepository>();

mock.Setup(x => x.GetArticleAsync(It.IsAny<int>()))
    .ReturnsAsync(new Article { Id = 1, Title = "First Article" });

Awaiting GetArticleAsync(1) in the code under test now yields that article, synchronously and without a thread hop, because the task is already complete before the await is reached.

Using plain Returns() here is the mistake worth knowing. Returns(article) will not compile, because the setup expects a Task<Article>, and Returns(Task.FromResult(article)) does compile. That is what ReturnsAsync() is shorthand for, and what older Moq code looks like.

When the value has to depend on the arguments, ReturnsAsync also accepts a lambda that receives them, so ReturnsAsync((int id) => _articles.First(a => a.Id == id)) returns a different article per call.

SetupSequence() handles the case where successive calls should return different values: a retry test that fails once and then succeeds is the usual reason. Each ReturnsAsync() chained onto the sequence covers one call, in order.

The same argument-driven idea is behind returning a value that was passed into a mocked method.

How Do We Mock a Method That Returns a Plain Task?

When the method returns Task with no result, there is no value to wrap, so ReturnsAsync() does not apply. The setup returns a completed task directly.

mock.Setup(x => x.SaveAsync(It.IsAny<Article>()))
    .Returns(Task.CompletedTask);

This is the single most common Moq async mistake. A void-shaped async method looks like it should use the async setup method, ReturnsAsync() does not compile against it, and the fix is the older-looking Returns().

Leaving the setup off entirely fails in a stranger way. A loose mock returns an already-completed task for an unconfigured Task<T> method, so the await succeeds and hands back default(T). Our own code throws on that null a few lines later.

That failure is worth recognising on sight, because the stack trace points at our own production code rather than at the test that forgot a setup. A strict mock (new Mock<T>(MockBehavior.Strict)) turns it into an explicit “no setup” error instead.

Two related cases follow the same rule: use Returns(Task.FromException(ex)) to fault the task, or ThrowsAsync(ex), which reads better and does the same thing.

The Repository Project We Are Going to Test

This project MockAsynchrounousMethods.Repository represents a Repository Pattern that connects our source code with a fake database — the same repository shape we would reach for when unit testing controllers with Moq.

Since the repository creation is not this article’s main goal, we are not going to show all the classes and interfaces, but the main class that we are going to test. That said, let’s inspect the ArticleRepository class:

public class ArticleRepository : IArticleRepository
{
    private readonly IFakeDbArticles _fakeDbArticle;

    public ArticleRepository(IFakeDbArticles fakeDbArticle)
    {
        _fakeDbArticle = fakeDbArticle;
    }

    public async Task<ArticleDbModel?> GetArticleAsync(int id)
    {
        return await _fakeDbArticle.GetByIdAsync(id);
    }

    public async Task<IEnumerable<ArticleDbModel>> GetAllArticlesAsync()
    {
        return await _fakeDbArticle.GetAsync();
    }
}

This class implements the IArticleRepository interface. Note that all these methods are asynchronous, and they simply retrieve data from the FakeDbArticles class:

public class FakeDbArticles : List<ArticleDbModel>, IFakeDbArticles
{
    private readonly static List<ArticleDbModel> _articles = Populate();

    private static List<ArticleDbModel> Populate()
    {
        var result = new List<ArticleDbModel>()
        {
            new ArticleDbModel
            {
                Id = 1,
                Title = "First Article",
                LastUpdate = DateTime.Now
            },
           ...
        };

        return result;
    }

    public async Task<IEnumerable<ArticleDbModel>> GetAsync()
    {
        return await Task.FromResult(_articles);
    }

    public async Task<ArticleDbModel?> GetByIdAsync(int id)
    {
        return await Task.FromResult(_articles.FirstOrDefault(x => x.Id == id));
    }
}

For further details, you can check the full implementation of this Repository in this article’s source code.

Is this material useful to you? Consider subscribing and get ASP.NET Core Web API Best Practices eBook for FREE!

XUnit Test Project to Mock Asynchronous Methods

Now that the repository is ready, let’s create MockAsynchronousMethods.Tests XUnit Test Project — see unit testing with xUnit if the framework itself is new to us.

Once the project is ready, let’s add the MockAsynchronousMethods.Repository as a reference by right-clicking in the dependencies and then Add Project Reference.

For the next step, we need to install the Moq Framework:

Install-Package Moq

Once it is done, let’s create a Mock folder, and, inside it, aFakeDbArticleMock class that inherits from Mock<IFakeDbArticles>.

public class FakeDbArticleMock : Mock<IFakeDbArticles>
{
}

And a FakeDb class to hold a list with some articles:

public static class FakeDb
{
    public static List<ArticleDbModel> Articles = new List<ArticleDbModel>()
    {
        new ArticleDbModel
        {
            Id = 1,
            Title = "First Article",
            LastUpdate = DateTime.Now
        },
        new ArticleDbModel
        {
            Id = 2,
            Title = "Second title",
            LastUpdate = DateTime.Now
        },
        new ArticleDbModel
        {
            Id = 3,
            Title = "Third title",
            LastUpdate = DateTime.Now
        }
    };
}

Now, let’s start the setup of our mocks:

public FakeDbArticleMock GetByIdAsync()
{
    Setup(x => x.GetByIdAsync(It.IsAny<int>()))
        .ReturnsAsync(FakeDb.Articles.First());

    return this;
}

In this method, we set up an asynchronous mock call to the GetByIdAsync method from the IFakeDbArticle interface. This method returns the first article from the list we created inside the FakeDb class.

When we mock an asynchronous method, instead of using a standard Return(...) method, we use the ReturnsAsync(...) method, which makes our mock asynchronous.

You can check the other mock methods in the full FakeDbArticleMock class implementation here.

Now that all mocks are ready, it is time to test our code.

Is this material useful to you? Consider subscribing and get ASP.NET Core Web API Best Practices eBook for FREE!

At the root of the test project, let’s create an ArticleRepositoryTest class, responsible for holding every test method of this article.

And then, let’s create a test method:

[Fact]
public async Task GivenAMockedDatabase_WhenRequestingAnExistingArticleAsynchronously_ThenReturnASingleArticle()
{
    var mockArticleRepository = new FakeDbArticleMock()
        .GetByIdAsync();

    var articleRepository = new ArticleRepository(mockArticleRepository.Object);
    var result = await articleRepository.GetArticleAsync(1);

    Assert.NotNull(result);
    Assert.Equal(FakeDb.Articles.First(), result);
}

First, we instantiate the FakeDbArticleMock class and indicate which setup we want to use for this test. Then, it is necessary to instantiate the repository we want to test and inject the mock instance into it.

Finally, we call the method we are testing and assert the results.

You can check the other test methods in our source code.

How Do We Verify an Async Method Was Called?

Verification is not async. Verify() inspects calls Moq already recorded, so there is no awaiting to do and no VerifyAsync() to look for.

mockRepository.Verify(x => x.GetByIdAsync(1), Times.Once);

The catch is ordering. The call is only recorded once the code under test has actually invoked it, so the awaited call must complete before we verify. That holds as long as the test method itself is async Task and every call in it is awaited.

An async void test is where this breaks. The test method returns at the first await, the runner reports success, and Verify() either runs against nothing or never runs at all. Microsoft’s asynchronous-programming guidance says to “Return ‘async void’ only from event handlers”, listing test difficulty among its reasons. Test methods return Task, always.

It.IsAny<int>() matches any argument when the value does not matter, and Times.Never is the assertion for a caching test: proving the second call did not reach the repository is usually the point.

For a deeper look at asserting call counts and argument matchers together, see using Moq to determine if a method was called.

The method returnsSetupNotes
Task.ReturnsAsync(value)Completed task wrapping value
Task, value per call.ReturnsAsync((int id) => Find(id))Lambda receives the call arguments
Task, sequence of values.SetupSequence(...).ReturnsAsync(a).ReturnsAsync(b)One value per successive call
Task (no result).Returns(Task.CompletedTask)ReturnsAsync() does not apply
ValueTask.ReturnsAsync(value)Supported the same way
IAsyncEnumerable.Returns(items.ToAsyncEnumerable())Not an awaitable. No ReturnsAsync; ToAsyncEnumerable() needs the System.Linq.Async package
Throwing async.ThrowsAsync(new NotFoundException())Faults the task rather than throwing at setup
Delay before returning.ReturnsAsync(value, TimeSpan.FromMilliseconds(50))For timeout and cancellation tests
Verify it was awaitedmock.Verify(x => x.GetByIdAsync(1), Times.Once)Same as sync: Verify is not async

One thing worth knowing before picking Moq for a new project: SponsorLink shipped in Moq 4.20.0 (August 2023) and was removed in 4.20.2 — the current 4.20.72 is clean. Moq has also shipped no release in roughly 23 months, which is worth weighing when choosing a mocking library in 2026; effective mocking with NSubstitute covers the same ground with a library that is still actively maintained.

Conclusion

In this article, we’ve learned how to mock asynchronous methods using the Moq framework, using ReturnsAsync() for methods returning Task<T> and Returns(Task.CompletedTask) for methods returning a plain Task. We’ve also seen how to verify that an async method was actually called, and the pitfall of doing that from an async void test.

Tested with .NET 10.0.10.