Данное руководство устарело. Актуальное руководство: Руководство по ASP.NET Core 7
В прошлой теме в проект был добавлен Moq фреймворк и протестировано получение данных из репозитория. Теперь определим в контроллере 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()); } public IActionResult GetUser(int? id) { if (!id.HasValue) return BadRequest(); User user = repo.Get(id.Value); if (user == null) return NotFound(); return View(user); } public IActionResult AddUser() => View(); [HttpPost] public IActionResult AddUser(User user) { if (ModelState.IsValid) { repo.Create(user); return RedirectToAction("Index"); } return View(user); } } }
Здесь добавлены методы GetUser()
, который возвращает объект из репозитория по id, и метод AddUser()
, который добавляет объект в репозиторий.
Для их тестирования определим следующие тесты:
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; } [Fact] public void AddUserReturnsViewResultWithUserModel() { // Arrange var mock = new Mock<IRepository>(); var controller = new HomeController(mock.Object); controller.ModelState.AddModelError("Name", "Required"); User newUser = new User(); // Act var result = controller.AddUser(newUser); // Assert var viewResult = Assert.IsType<ViewResult>(result); Assert.Equal(newUser, viewResult?.Model); } [Fact] public void AddUserReturnsARedirectAndAddsUser() { // Arrange var mock = new Mock<IRepository>(); var controller = new HomeController(mock.Object); var newUser = new User() { Name = "Ben" }; // Act var result = controller.AddUser(newUser); // Assert var redirectToActionResult = Assert.IsType<RedirectToActionResult>(result); Assert.Null(redirectToActionResult.ControllerName); Assert.Equal("Index", redirectToActionResult.ActionName); mock.Verify(r => r.Create(newUser)); } [Fact] public void GetUserReturnsBadRequestResultWhenIdIsNull() { // Arrange var mock = new Mock<IRepository>(); var controller = new HomeController(mock.Object); // Act var result = controller.GetUser(null); // Arrange Assert.IsType<BadRequestResult>(result); } [Fact] public void GetUserReturnsNotFoundResultWhenUserNotFound() { // Arrange int testUserId = 1; var mock = new Mock<IRepository>(); mock.Setup(repo => repo.Get(testUserId)) .Returns(null as User); var controller = new HomeController(mock.Object); // Act var result = controller.GetUser(testUserId); // Assert Assert.IsType<NotFoundResult>(result); } [Fact] public void GetUserReturnsViewResultWithUser() { // Arrange int testUserId = 1; var mock = new Mock<IRepository>(); mock.Setup(repo => repo.Get(testUserId)) .Returns(GetTestUsers().FirstOrDefault(p => p.Id == testUserId)); var controller = new HomeController(mock.Object); // Act var result = controller.GetUser(testUserId); // Assert var viewResult = Assert.IsType<ViewResult>(result); var model = Assert.IsType<User>(viewResult.ViewData.Model); Assert.Equal("Tom", model.Name); Assert.Equal(35, model.Age); Assert.Equal(testUserId, model.Id); } } }
Для post-версии метода AddUser контроллера HomeController существуют два варианта событий. При валидной модели она добавляется в репозиторий, и происходит передаресация на метод Index:
if (ModelState.IsValid) { repo.Create(user); return RedirectToAction("Index"); }
Для тестирования этой ситуации в классе тестов определен метод AddUserReturnsARedirectAndAddsUser()
:
public void AddUserReturnsARedirectAndAddsUser() { // Arrange var mock = new Mock<IRepository>(); var controller = new HomeController(mock.Object); var newUser = new User() { Name = "Ben" }; // Act var result = controller.AddUser(newUser); // Assert var redirectToActionResult = Assert.IsType<RedirectToActionResult>(result); Assert.Equal("Index", redirectToActionResult.ActionName); mock.Verify(r => r.Create(newUser)); }
В случае переадресации возвращаемым типом должен быть RedirectToActionResult. Его свойство ActionName указывает на метод для переадресации - он должен представлять метод Index.
В тестах мы легко можем взять результат тестируемых методов и сравнить этот результат с определенным значением, чтобы понять, правильно ли все работает. Но не все методы возвращают определенные значения. Некоторые методы возвращают тип void, однако и тоже необходимо тестировать. И чтобы протестировать подобные методы, в классе Mock определен метод Verify. Так, в данном случае мы верифицируем, что метод Create у репозитория точно был вызван:
mock.Verify(r => r.Create(newUser));
Вторая ситуация в методе AddUser в контроллере представляет передачу обратно в представление некорректной модели:
public IActionResult AddUser(User user) { if (ModelState.IsValid) { //............ } return View(user); }
Для тестирования данной ситуации определен метод AddUserReturnsViewResultWithUserModel()
:
public void AddUserReturnsViewResultWithUserModel() { // Arrange var mock = new Mock<IRepository>(); var controller = new HomeController(mock.Object); controller.ModelState.AddModelError("Name", "Required"); User newUser = new User(); // Act var result = controller.AddUser(newUser); // Assert var viewResult = Assert.IsType<ViewResult>(result); Assert.Equal(newUser, viewResult?.Model); }
Для имитации некорретной модели здесь добавляется ошибка модели с помощью вызова controller.ModelState.AddModelError()
.
Для проверки теста мы устанавливаем тип результата и сравниваем переданную обратно в представление модель - она должна быть эквивалентна исходной модели newUser.
Стоит отметить, что здесь в тесте не будет работать проверка if(ModelState.IsValid)
, потому что в тесте не учитывается механизм привязки модели,
а поэтому и ошибок модели в контроллере нет.
Метод GetUser в контроллере HomeController подразумевает три различные ситуации. Первая ситуация представляет некорректную передачу id:
if (!id.HasValue) return BadRequest();
Для тестирования этого куска кода определен метод GetUserReturnsBadRequestResultWhenIdIsNull()
:
public void GetUserReturnsBadRequestResultWhenIdIsNull() { // Arrange var mock = new Mock<IRepository>(); var controller = new HomeController(mock.Object); // Act var result = controller.GetUser(null); // Arrange Assert.IsType<BadRequestResult>(result); }
На выходе из метода должен возвращаться объект BadRequestResult
, что мы и проверяем.
Вторая ситуация связана с тем, что в репозитории может не оказаться объект с переданным id, поэтому метод возвращает NotFoundResult:
User user = repo.Get(id.Value); if (user == null) return NotFound();
Для ее тестирования предназначен метод GetUserReturnsNotFoundResultWhenUserNotFound()
:
public void GetUserReturnsNotFoundResultWhenUserNotFound() { int testUserId = 1; var mock = new Mock<IRepository>(); mock.Setup(repo => repo.Get(testUserId)) .Returns(null as User); var controller = new HomeController(mock.Object); var result = controller.GetUser(testUserId); Assert.IsType<NotFoundResult>(result); }
Тест проверяет возвращаемый результат - он должен представлять объект NotFoundResult
.
Третья ситуация в методе GetUser контроллера HomeController связана с получением объекта из репозиторяи и передачи его в представление:
return View(user);
Чтобы протестировать эту ситуацию, определен метод GetUserReturnsViewResultWithUser()
:
public void GetUserReturnsViewResultWithUser() { // Arrange int testUserId = 1; var mock = new Mock<IRepository>(); mock.Setup(repo => repo.Get(testUserId)) .Returns(GetTestUsers().FirstOrDefault(p => p.Id == testUserId)); var controller = new HomeController(mock.Object); // Act var result = controller.GetUser(testUserId); // Assert var viewResult = Assert.IsType<ViewResult>(result); var model = Assert.IsType<User>(viewResult.ViewData.Model); Assert.Equal("Tom", model.Name); Assert.Equal(35, model.Age); Assert.Equal(testUserId, model.Id); }
Здесь для метода репозитория Get в качестве возвращаемого результата устанавливается объект с id=1, который имеется в списке, создаваемом в методе GetTestUsers.
Сам тест проверяет тип результата - ViewResult, тип модели - User, а также значения модели, которые нам известны.
Ну и в конце запустим тесты и убедимся, что методы контроллера соответствуют этим тестам: