Данное руководство устарело. Актуальное руководство: Руководство по ASP.NET Core 7
Нередко методы контроллера или функционал тестируемых классов использует какие-то другие внешние классы. Например, при работе с базой данных может использоваться контекст данных или репозиторий, которые определены как отдельные классы.
Например, пусть в проекте 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:
Теперь протестируем этот контроллер, определив следующий тест:
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());
Запустим тест и убедимся, что он проходит: