Testando Entity Framework com Moq

Neste artigo vou explicar como fazer teste unitário para Entity Framework utilizando o pacote Moq do C#. Com ele podemos simular comportamentos específicos em nossas classes e configurar cenários de testes.

Antes de começar, vamos criar uma estrutura simples de objeto, entidade e respositório com Entity Framework.

namespace LuizPauloPradoBlog.Repository.Model
{
    public class Car
    {
        public int Id { get; set; }
        public string Model { get; set; }
        public string Name { get; set; }
        public int YearOfManufacture { get; set; }
    }
}
namespace LuizPauloPradoBlog.Repository.Context
{
    public class CarShoppingContext : DbContext
    {
        public CarShoppingContext() { }

        public virtual DbSet<Car> Cars { get; set; }
    }
}
namespace LuizPauloPradoBlog.Repository
{
    public class CarRepository : ICarRepository
    {
        private readonly CarShoppingContext _context;

        public CarRepository(CarShoppingContext context)
        {
            _context = context;
        }

        public IList<Car> GetAll()
        {
            return _context.Cars.ToList();
        }

        public Car Get(int id)
        {
            return _context.Cars.FirstOrDefault(x => x.Id == id);
        }

        public void Add(Car car)
        {
            _context.Cars.Add(car);
            _context.SaveChanges();
        }
    }
}

Com essa estrutura básica criada, basta adicionar um novo projeto do tipo Unit Test no Visual Studio clicando em Solution > Add > New Project > Test > Unit Test Project.

Um ponto importante antes de começar a escrever os testes é lembrar-se de instalar o pacote Moq.

Install-Package Moq -Version 4.5.10

Screenshot_1

Moq (ou Mock) é um framework para realizar e configurar simulações em métodos e classes. A configuração de um objeto usando Moq é bem conveniente para se criar testes unitários e simular comportamentos que validem o funcionamento em determinados cenários.

Para inciar, vamos fazer um teste que chama o repositório e realiza a inclusão de um novo registro. Nesse exemplo vamos fazer o teste criando o método ShouldCreateNewCar(). Para isso, é preciso fazer um mock do DbSet do objeto, um mock do Context do Entity Framework e um mock da classe de repositório.

A notação consiste em instanciar um objeto Mock informando o tipo de objeto que deverá ser simulado. A linha de código fica da seguinte maneira: new Mock.

Logo abaixo temos um mock do nosso objeto DbSet.

var _dbSet = new Mock<DbSet<Car>>();

O próximo passo é fazer o mesmo com o Context do Entity Framework. Aqui entra o conceito de efetuar uma configuração (usando o método Setup) do nosso objeto mock para especificar um comportamento do mesmo. Neste caso, devemos configurá-lo para retornar um DbSet especifico ao chamar a nossa entidade de exemplo.

var _dbSet = new Mock<DbSet<Car>>();

var _context = new Mock<CarShoppingContext>();
_context.Setup(x => x.Cars).Returns(_dbSet.Object);

Com isso feito, usaremos os objetos mock como parâmetro na classe de repositório. Ela será instanciada no teste e receberá os objetos que criamos. Isso irá permitir testar a classe usando objetos de simulação que possuem o comportamento que configuramos anteriormente.

namespace LuizPauloPradoBlog.WebApi.Tests
{
    [TestClass]
    public class CarShoppingContextTestWithMock
    {
        [TestMethod]
        public void ShouldCreateNewCar()
        {
            var _dbSet = new Mock<DbSet<Car>>();

            var _context = new Mock<CarShoppingContext>();
            _context.Setup(x => x.Cars).Returns(_dbSet.Object);

            var _repository = new CarRepository(_context.Object);

            var sampleCar = new Car() { Id = 3, Name = "Civic", Model = "Honda Civic SE", YearOfManufacture = 2016 };

            _repository.Add(sampleCar);

            _dbSet.Verify(m => m.Add(It.IsAny<Car>()), Times.Once());
            _context.Verify(m => m.SaveChanges(), Times.Once());
        }
    }
}

Após isso, para validar o comportamento do que foi executado, usaremos o método Verify para confirmar que um objeto foi adicionado ao DbSet e também confirmar que o método de SaveChanges do Context foi devidamente acionado.

No segundo teste vamos chamar o repositório e realizar a listagem de alguns registros. Nesse exemplo vamos criar o método ShouldGetAllCars().

A preparação dos objetos mock seguem a mesma lógica do teste anterior. A diferença fica por conta de incluir registros de teste no DbSet e efetuar as configurações de um IQueryable que represente o nosso DbSet.

namespace LuizPauloPradoBlog.WebApi.Tests
{
    [TestClass]
    public class CarShoppingContextTestWithMock
    {
        [TestMethod]
        public void ShouldGetAllCars()
        {
            var sampleData = new List<Car>()
            {
                new Car() { Id = 1, Name = "Fiesta", Model = "Fiesta SE", YearOfManufacture = 2015 },
                new Car() { Id = 2, Name = "Golf", Model = "Golf Sport", YearOfManufacture = 2015 }
            }.AsQueryable();

            var _dbSet = new Mock<DbSet<Car>>();
            _dbSet.As<IQueryable<Car>>().Setup(x => x.Provider).Returns(sampleData.Provider);
            _dbSet.As<IQueryable<Car>>().Setup(x => x.Expression).Returns(sampleData.Expression);
            _dbSet.As<IQueryable<Car>>().Setup(x => x.ElementType).Returns(sampleData.ElementType);
            _dbSet.As<IQueryable<Car>>().Setup(x => x.GetEnumerator()).Returns(sampleData.GetEnumerator());

            var _context = new Mock<CarShoppingContext>();
            _context.Setup(x => x.Cars).Returns(_dbSet.Object);

            var _repository = new CarRepository(_context.Object);

            var cars = _repository.GetAll();

            Assert.IsNotNull(cars);
            Assert.IsTrue(cars.Any());
        }
    }
}

Neste caso, efetuamos a criação de uma lista de registros de exemplo como IQueryable e configuramos o mock do DbSet para utilizá-la. Ao chamar o método de GetAll() no repositório, o mesmo deve retornar exatamente os dados de exemplo que configuramos.

Por fim, podemos organizar melhor a configuração dos objetos de mock no método Initialize da classe de testes. O resultado ficou assim:

namespace LuizPauloPradoBlog.WebApi.Tests
{
    [TestClass]
    public class CarShoppingContextTestWithMock
    {
        private Mock<DbSet<Car>> _dbSet;
        private Mock<CarShoppingContext> _context;
        private ICarRepository _repository;

        [TestInitialize]
        public void Initialize()
        {
            var sampleData = new List<Car>()
            {
                new Car() { Id = 1, Name = "Fiesta", Model = "Fiesta SE", YearOfManufacture = 2015 },
                new Car() { Id = 2, Name = "Golf", Model = "Golf Sport", YearOfManufacture = 2015 }
            }.AsQueryable();

            _dbSet = new Mock<DbSet<Car>>();
            _dbSet.As<IQueryable<Car>>().Setup(x => x.Provider).Returns(sampleData.Provider);
            _dbSet.As<IQueryable<Car>>().Setup(x => x.Expression).Returns(sampleData.Expression);
            _dbSet.As<IQueryable<Car>>().Setup(x => x.ElementType).Returns(sampleData.ElementType);
            _dbSet.As<IQueryable<Car>>().Setup(x => x.GetEnumerator()).Returns(sampleData.GetEnumerator());

            _context = new Mock<CarShoppingContext>();
            _context.Setup(x => x.Cars).Returns(_dbSet.Object);

            _repository = new CarRepository(_context.Object);
        }

        [TestMethod]
        public void ShouldCreateNewCar()
        {
            var sampleCar = new Car() { Id = 3, Name = "Civic", Model = "Honda Civic SE", YearOfManufacture = 2016 };
            _repository.Add(sampleCar);

            _dbSet.Verify(m => m.Add(It.IsAny<Car>()), Times.Once());
            _context.Verify(m => m.SaveChanges(), Times.Once());
        }

        [TestMethod]
        public void ShouldGetAllCars()
        {
            var cars = _repository.GetAll();

            Assert.IsNotNull(cars);
            Assert.IsTrue(cars.Any());
        }
    }
}

O projeto de exemplo pode ser encontrado no meu GitHub.

Pra quem quer pesquisar mais, o framework Moq possui muitas outras opções de métodos disponíveis, vale a pena conferir a documentação dele.

Se ficou alguma dúvida, comenta aqui no blog que eu tento ajudar. 🙂

Um comentário sobre “Testando Entity Framework com Moq

Deixe um comentário