in C#

Testes com Moq e Entity Framework Async (EF6)

O Entity Framework possui diversos recursos que facilitam o desenvolvimento, um dos recursos que considero muito importante é a possibilidade de realizar queries de maneira assíncrona (http://msdn.microsoft.com/en-us/data/jj819165.aspx). Escrevi um post mostrando algumas maneiras de implementar testes com Entity Framework (Testes com Moq e Entity Framework (EF6)).

A ideia deste post é mostrar como implementar testes em cenários de queries assíncronas, como no post anterior vou continuar utilizando Moq e terei como base o projeto que publiquei no GitHub: https://github.com/rodolfofadino/TestesComMoqEEntityFramework

Para começar, no LivroService.cs vou criar um método que chamará GetLivrosAtivosAsync, deixando a classe da seguinte maneira:

using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Threading.Tasks;

namespace ProjetoLivro.BL
{
    public class LivroService
    {
        private LivroContext _context { get; set; }
        public LivroService(LivroContext context)
        {
            _context = context;
        }

        public List<Livro> GetLivrosAtivos()
        {
            return _context.Livros.Where(a => a.Status== true).ToList();
        }

        public Task<List<Livro>> GetLivrosAtivosAsync()
        {
            return _context.Livros.Where(a => a.Status == true).ToListAsync();
        }

        public void Salvar(Livro livro)
        {
            _context.Livros.Add(livro);
            _context.SaveChanges();
        }
    }
}

Após alterar o LivroService.cs, vou criar uma Action que utiliza este método, notem como é simples utilizar Tasks com ASP.NET MVC 4:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Web;
using System.Web.Mvc;
using ProjetoLivro.BL;

namespace ProjetoLivro.Web.Controllers
{
    public class LivroController : Controller
    {
        //Nao façam isso!!!
        private LivroService livroService = new LivroService(new LivroContext());

        public async Task<ActionResult> IndexAsync()
        {
            return View(await livroService.GetLivrosAtivosAsync());
        }
     //...//
    }
}

Testando queries com Async

Para testar este tipo de consulta será necessário criar uma classe que implemente IDbAsyncQueryProvider, como no exemplo abaixo, notem que criei mais duas classes que serão necessárias: TestDbAsyncEnumerable e TestDbAsyncEnumerator.

internal class TestDbAsyncQueryProvider<TEntity> : IDbAsyncQueryProvider
{
    private readonly IQueryProvider _inner;

    internal TestDbAsyncQueryProvider(IQueryProvider inner)
    {
        _inner = inner;
    }

    public IQueryable CreateQuery(Expression expression)
    {
        return new TestDbAsyncEnumerable<TEntity>(expression);
    }

    public IQueryable<TElement> CreateQuery<TElement>(Expression expression)
    {
        return new TestDbAsyncEnumerable<TElement>(expression);
    }

    public object Execute(Expression expression)
    {
        return _inner.Execute(expression);
    }

    public TResult Execute<TResult>(Expression expression)
    {
        return _inner.Execute<TResult>(expression);
    }

    public Task<object> ExecuteAsync(Expression expression, CancellationToken cancellationToken)
    {
        return Task.FromResult(Execute(expression));
    }

    public Task<TResult> ExecuteAsync<TResult>(Expression expression, CancellationToken cancellationToken)
    {
        return Task.FromResult(Execute<TResult>(expression));
    }

}
internal class TestDbAsyncEnumerable<T> : EnumerableQuery<T>, IDbAsyncEnumerable<T>, IQueryable<T>
{
    public TestDbAsyncEnumerable(IEnumerable<T> enumerable)
        : base(enumerable)
    { }

    public TestDbAsyncEnumerable(Expression expression)
        : base(expression)
    { }

    public IDbAsyncEnumerator<T> GetAsyncEnumerator()
    {
        return new TestDbAsyncEnumerator<T>(this.AsEnumerable().GetEnumerator());
    }

    IDbAsyncEnumerator IDbAsyncEnumerable.GetAsyncEnumerator()
    {
        return GetAsyncEnumerator();
    }

    IQueryProvider IQueryable.Provider
    {
        get { return new TestDbAsyncQueryProvider<T>(this); }
    }
}
internal class TestDbAsyncEnumerator<T> : IDbAsyncEnumerator<T>
{
    private readonly IEnumerator<T> _inner;

    public TestDbAsyncEnumerator(IEnumerator<T> inner)
    {
        _inner = inner;
    }

    public void Dispose()
    {
        _inner.Dispose();
    }

    public Task<bool> MoveNextAsync(CancellationToken cancellationToken)
    {
        return Task.FromResult(_inner.MoveNext());
    }

    public T Current
    {
        get { return _inner.Current; }
    }

    object IDbAsyncEnumerator.Current
    {
        get { return Current; }
    }
}

Com estas classes criadas podemos implementar o teste do método assíncrono que adicionamos no LivroService.cs. Para isto, vou criar um teste chamado RetornaSomenteLivrosAtivosAsync, as maiores mudanças em relação a um teste de uma query síncrona com EF está no setup do GetAsyncEnumerator e no setup do Provider.

[TestMethod]
public async Task RetornaSomenteLivrosAtivosAsync()
{
    var data = new List<Livro>
    {
    new Livro { Nome = "Livro A", Status=true },
    new Livro { Nome = "Livro B", Status=false },
    new Livro { Nome = "Livro C", Status=true },
    }.AsQueryable();

    var mockSet = new Mock<DbSet<Livro>>();
    mockSet.As<IDbAsyncEnumerable<Livro>>()
    .Setup(m => m.GetAsyncEnumerator())
    .Returns(new TestDbAsyncEnumerator<Livro>(data.GetEnumerator()));

    mockSet.As<IQueryable<Livro>>()
    .Setup(m => m.Provider)
    .Returns(new TestDbAsyncQueryProvider<Livro>(data.Provider));

    mockSet.As<IQueryable<Livro>>().Setup(m => m.Expression).Returns(data.Expression);
    mockSet.As<IQueryable<Livro>>().Setup(m => m.ElementType).Returns(data.ElementType);
    mockSet.As<IQueryable<Livro>>().Setup(m => m.GetEnumerator()).Returns(data.GetEnumerator());

    var mockContext = new Mock<LivroContext>();
    mockContext.Setup(c => c.Livros).Returns(mockSet.Object);

    var service = new LivroService(mockContext.Object);
    var livros = await service.GetLivrosAtivosAsync();

    Assert.AreEqual(2, livros.Count);
    Assert.AreEqual("Livro A", livros[0].Nome);
    Assert.AreEqual("Livro C", livros[1].Nome);
}

Agora temos nosso teste executando com sucesso \o/

image

O projeto de exemplo está no meu GitHub: https://github.com/rodolfofadino/TestesComMoqEEntityFramework

Espero que este post seja útil, estou a disposição de dúvidas, criticas e sugestões.

abs

Rodolfo