Para simular um método assíncrono com o Moq, configure-o com ReturnsAsync() em vez de Returns(). Para um método que retorna Task<T>, ReturnsAsync(value) retorna uma tarefa já concluída que encapsula esse valor, então o código sob teste pode aguardá-la normalmente.

Para um método que retorna uma Task simples, sem resultado, não há nada a encapsular, então a configuração usa Returns(Task.CompletedTask). Essas duas linhas cobrem quase todos os mocks assíncronos que vamos escrever, e o restante do artigo mostra as duas aplicadas a um repositório real.

Para baixar o código-fonte deste artigo, você pode acessar nosso repositório no GitHub.

Como usar ReturnsAsync no Moq?

ReturnsAsync() é o método de configuração do Moq para qualquer método simulado cujo tipo de retorno seja Task<T>. Ele recebe o valor que queremos entregar a quem chama, e o Moq o encapsula em uma tarefa já concluída.

var mock = new Mock<IArticleRepository>();

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

Agora, aguardar GetArticleAsync(1) no código sob teste retorna esse artigo de forma síncrona e sem troca de thread, porque a tarefa já está concluída antes de a execução chegar ao await.

Usar o Returns() comum aqui é o erro que vale a pena conhecer. Returns(article) não compila, porque a configuração espera uma Task<ArticleDbModel>; já Returns(Task.FromResult(article)) compila. É para essa forma que ReturnsAsync() serve de atalho, e é ela que aparece no código mais antigo com Moq.

Quando o valor precisa depender dos argumentos, ReturnsAsync também aceita uma expressão lambda que os recebe, então ReturnsAsync((int id) => _articles.First(a => a.Id == id)) retorna um artigo diferente a cada chamada.

SetupSequence() trata o caso em que chamadas sucessivas devem retornar valores diferentes: um teste de novas tentativas que falha uma vez e depois é bem-sucedido é o motivo mais comum. Cada ReturnsAsync() encadeado na sequência cobre uma chamada, na ordem.

A mesma ideia, baseada nos argumentos, está por trás de retornar um valor que foi passado para um método simulado.

Como simular um método que retorna uma Task simples?

Quando o método retorna Task sem resultado, não há valor a encapsular, então ReturnsAsync() não se aplica. A configuração retorna diretamente uma tarefa concluída.

mock.Setup(x => x.SaveArticleAsync(It.IsAny<ArticleDbModel>()))
    .Returns(Task.CompletedTask);

Um método assíncrono no formato void parece pedir o método de configuração assíncrono, mas ReturnsAsync() não compila com ele, e a solução é o Returns(), de aparência mais antiga.

Omitir a configuração por completo leva a uma falha mais estranha. Um mock flexível (loose) retorna uma tarefa já concluída para um método Task<T> não configurado, então o await é bem-sucedido e retorna default(T), ou uma sequência vazia quando T é um array, IEnumerable ou IQueryable. O nosso próprio código lança uma exceção por causa desse null algumas linhas depois.

Vale a pena reconhecer essa falha logo de cara, porque o stack trace aponta para o nosso próprio código de produção, e não para o teste que esqueceu uma configuração. Já um mock estrito (new Mock<T>(MockBehavior.Strict)) transforma essa falha em um erro explícito de “configuração ausente”.

Um caso relacionado segue a mesma regra: use Returns(Task.FromException(ex)) para colocar a tarefa em estado de falha, ou ThrowsAsync(ex), que é mais legível e faz a mesma coisa.

O projeto de repositório que vamos testar

O projeto MockAsynchronousMethods.Repository representa um padrão Repository que conecta o nosso código-fonte a um banco de dados falso, o mesmo formato de repositório que usaríamos para criar testes unitários de controladores com o Moq.

Como a criação do repositório não é o objetivo principal deste artigo, não vamos mostrar todas as classes e interfaces, apenas a classe principal que vamos testar. Dito isso, vamos examinar a classe ArticleRepository:

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();
    }
}

Essa classe implementa a interface IArticleRepository. Observe que todos esses métodos são assíncronos e simplesmente obtêm dados da classe FakeDbArticles:

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));
    }
}

Para mais detalhes, você pode conferir a implementação completa desse repositório no código-fonte deste artigo.

The Web API Production Checklist, e-book gratuito em inglês

E-book gratuito

Sua Web API está pronta para produção?

33 itens para verificar antes de implantá-la, com a correção de cada um. Um PDF gratuito de 76 páginas para .NET 10.

O e-book está em inglês.

Baixe o checklist gratuito

PDF gratuito. Um único e-mail para enviá-lo. Cancele a inscrição quando quiser.

Projeto de teste xUnit para simular métodos assíncronos

Agora que o repositório está pronto, vamos criar o projeto de teste xUnit MockAsynchronousMethods.Tests. Veja o artigo sobre testes unitários com xUnit se o próprio framework ainda for novidade.

Com o projeto pronto, vamos adicionar MockAsynchronousMethods.Repository como referência, clicando com o botão direito nas dependências e depois em Add Project Reference.

Na próxima etapa, precisamos instalar o framework Moq:

Install-Package Moq

Feito isso, vamos criar uma pasta Mock e, dentro dela, uma classe FakeDbArticleMock que herda de Mock<IFakeDbArticles>.

using MockAsynchronousMethods.Repository.Interfaces;
using Moq;

public class FakeDbArticleMock : Mock<IFakeDbArticles>
{
}

E uma classe FakeDb para guardar uma lista com alguns artigos:

using MockAsynchronousMethods.Repository.DbModels;

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
        }
    };
}

Agora, vamos começar a configurar os nossos mocks:

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

    return this;
}

Nesse método, configuramos uma chamada assíncrona simulada ao método GetByIdAsync da interface IFakeDbArticles. Esse método retorna o primeiro artigo da lista que criamos dentro da classe FakeDb.

Quando simulamos um método assíncrono que retorna um valor, em vez do método Returns(...) comum, usamos o método ReturnsAsync(...), que faz o nosso mock retornar uma tarefa concluída.

Você pode conferir os outros métodos de mock na implementação completa da classe FakeDbArticleMock aqui.

Agora que todos os mocks estão prontos, é hora de testar o nosso código.

The Web API Production Checklist, e-book gratuito em inglês

E-book gratuito

Sua Web API está pronta para produção?

33 itens para verificar antes de implantá-la, com a correção de cada um. Um PDF gratuito de 76 páginas para .NET 10.

O e-book está em inglês.

Baixe o checklist gratuito

PDF gratuito. Um único e-mail para enviá-lo. Cancele a inscrição quando quiser.

Na raiz do projeto de teste, vamos criar uma classe ArticleRepositoryTests, responsável por reunir todos os métodos de teste deste artigo. O arquivo dela começa com diretivas using para MockAsynchronousMethods.Repository, MockAsynchronousMethods.Tests.Mock e Moq.

Em seguida, vamos criar um método de teste:

[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);
}

Primeiro, instanciamos a classe FakeDbArticleMock e indicamos qual configuração queremos usar neste teste. Depois, precisamos instanciar o repositório que queremos testar e injetar nele a instância do mock.

Por fim, chamamos o método que estamos testando e fazemos as asserções sobre os resultados.

Você pode conferir os outros métodos de teste no nosso código-fonte.

Como verificar se um método assíncrono foi chamado?

A verificação não é assíncrona. Verify() inspeciona as chamadas que o Moq já registrou, então não há nada a aguardar nem um VerifyAsync() a procurar.

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

O porém está na ordem. A chamada só é registrada depois que o código sob teste realmente a faz, então a chamada aguardada precisa terminar antes de verificarmos. Isso vale desde que o próprio método de teste seja async Task e todas as chamadas nele sejam aguardadas.

É em um teste async void que isso fica frágil. O xUnit.net 2.9 ainda espera que ele termine, mas o xUnit.net v3 não oferece mais suporte a testes async void. As orientações da Microsoft sobre programação assíncrona dizem: “Retorne ‘async void’ somente em manipuladores de eventos”, e citam a dificuldade de testar entre os motivos. Métodos de teste retornam Task, sempre.

It.IsAny<int>() corresponde a qualquer argumento quando o valor não importa, e Times.Once depois de duas chamadas é a asserção de um teste de cache: provar que a segunda chamada não chegou ao repositório costuma ser o objetivo.

Para se aprofundar nas asserções sobre a quantidade de chamadas, veja como usar o Moq para determinar se um método foi chamado.

O método retornaConfiguraçãoObservações
Task<T>.ReturnsAsync(value)Tarefa concluída que encapsula o valor
Task<T>, valor por chamada.ReturnsAsync((int id) => Find(id))A lambda recebe os argumentos da chamada
Task<T>, sequência de valores.SetupSequence(...).ReturnsAsync(a).ReturnsAsync(b)Um valor para cada chamada sucessiva
Task (sem resultado).Returns(Task.CompletedTask)ReturnsAsync() não se aplica
ValueTask<T>.ReturnsAsync(value)Funciona da mesma forma
IAsyncEnumerable.Returns(items.ToAsyncEnumerable())Não é aguardável. Sem ReturnsAsync; ToAsyncEnumerable() já vem no .NET 10, sem precisar de pacote
Lançar exceção de forma assíncrona.ThrowsAsync(new NotFoundException())Coloca a tarefa em estado de falha em vez de lançar na chamada
Atraso antes de retornar.ReturnsAsync(value, TimeSpan.FromMilliseconds(50))Para testes de tempo limite e cancelamento
Verificar se foi chamadomock.Verify(x => x.GetByIdAsync(1), Times.Once)Igual ao síncrono: Verify não é assíncrono

Algo que vale a pena saber antes de escolher o Moq para um novo projeto: o SponsorLink foi incluído no Moq 4.20.0 (agosto de 2023) e removido na 4.20.2, então a versão atual, 4.21.0, está livre dele. O Moq não lançou nenhuma versão entre a 4.20.72, em setembro de 2024, e a 4.21.0, em setembro de 2026, o que convém considerar na escolha de uma biblioteca de mocks. O nosso guia sobre mocks eficazes com o NSubstitute cobre o mesmo assunto com uma biblioteca que continua sendo mantida ativamente.

Conclusão

Neste artigo, aprendemos a simular métodos assíncronos com o framework Moq, usando ReturnsAsync() para métodos que retornam Task<T> e Returns(Task.CompletedTask) para métodos que retornam uma Task simples. Vimos também como verificar se um método assíncrono foi de fato chamado e a armadilha de fazer isso em um teste async void.

Testado com o .NET 10.0.10.