Фреймворк Moq и moq-объекты

Данное руководство устарело. Актуальное руководство: Руководство по ASP.NET Core 7

Последнее обновление: 21.12.2019

Нередко методы контроллера или функционал тестируемых классов использует какие-то другие внешние классы. Например, при работе с базой данных может использоваться контекст данных или репозиторий, которые определены как отдельные классы.

Например, пусть в проекте ASP.NET Core определена модель User:

using System.ComponentModel.DataAnnotations;

namespace UnitTestApp.Models
{
    public class User
    {
        public int Id { get; set; }
        [Required]
        public string Name { get; set; }
        public int Age { get; set; }
    }
}

Для работы с данными добавим общий интерфейс репозитория:

using System.Collections.Generic;

namespace UnitTestApp.Models
{
    public interface IRepository
    {
        IEnumerable<User> GetAll();
        User Get(int id);
        void Create(User user);
    }
}

Этот интерфейс может реализоваться разными классами, которые используют разные источники данных - базу данных, файлы xml, список объектов в памяти, внешние сервисы и так далее. Нам это не важно, при необходимости мы можем предусмотреть и установить через встроенный механизм Dependency Injection любую реализацию для данного интерфейса.

Далее используем интерфейс в контроллере HomeController:

using Microsoft.AspNetCore.Mvc;
using UnitTestApp.Models;

namespace UnitTestApp.Controllers
{
    public class HomeController : Controller
    {
        IRepository repo;
        public HomeController(IRepository r)
        {
            repo = r;
        }
        public IActionResult Index()
        {
            return View(repo.GetAll());
        }
    }
}

В конструкторе контроллер получает нужную реализацию интерфейса IRepository. Для получения данных предусмотрен метод Index, который передает их в представление.

Отмечу, что данном случае нам можно не создавать реализацию интерфейса IRepository, устанавливать зависимости в классе Startup. Мы можем протестировать, имея лишь интерфейс репозитория.

Вначале добавим через NuGet в проект тестов пакет Moq:

Добавление Moq в проект ASP.NET Core

Теперь протестируем этот контроллер, определив следующий тест:

using Microsoft.AspNetCore.Mvc;
using UnitTestApp.Controllers;
using UnitTestApp.Models;
using Moq;
using Xunit;
using System.Collections.Generic;
using System.Linq;

namespace UnitTestApp.Tests
{
    public class HomeControllerTests
    {
        [Fact]
        public void IndexReturnsAViewResultWithAListOfUsers()
        {
            // Arrange
            var mock = new Mock<IRepository>();
            mock.Setup(repo=>repo.GetAll()).Returns(GetTestUsers());
            var controller = new HomeController(mock.Object);

            // Act
            var result = controller.Index();

            // Assert
            var viewResult = Assert.IsType<ViewResult>(result);
            var model = Assert.IsAssignableFrom<IEnumerable<User>>(viewResult.Model);
            Assert.Equal(GetTestUsers().Count, model.Count());
        }
        private List<User> GetTestUsers()
        {
            var users = new List<User>
            {
                new User { Id=1, Name="Tom", Age=35},
                new User { Id=2, Name="Alice", Age=29},
                new User { Id=3, Name="Sam", Age=32},
                new User { Id=4, Name="Kate", Age=30}
            };
            return users;
        }
    }
}

Moq предназначен для имитации объектов или для создания так называемых фейковых объектов. В данном случае имитируется функциональность репозитория. Для этого объект Mock типизируется соответствующим типом: var mock = new Mock<IRepository>()

Затем выполняется настройка mock объекта с помощью метода Setup. Так как нам надо имитировать возвращение методом GetAll() набора объектов, то данный метод вызывается в методе Setup, а с помощью метода Returns определяем данный набор объектов. Набор объектов, который используется для теста, возвращается с помощью вспомогательного метода GetTestUsers.

Поскольку контроллер HomeController теперь в конструкторе принимает объект репозитория, то мы можем передать в конструктор мок-объект, который имитирует функциональность репозитория: HomeController controller = new HomeController(mock.Object)

Для проверки работоспособности метода Index здесь проверяются три условия. Является ли возвращаемый результат объектом ViewResult:

var viewResult = Assert.IsType<ViewResult>(result);

Передается ли в представление в качестве модели объект IEnumerable<User>:

var model = Assert.IsAssignableFrom<IEnumerable<User>>(viewResult.Model);

И также проверяется количество объектов, которые передаются в представление:

Assert.Equal(GetTestUsers().Count, model.Count());

Запустим тест и убедимся, что он проходит:

Moq в ASP.NET Core
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850