Тестирование контроллера

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

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

В прошлой теме в проект был добавлен 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, а также значения модели, которые нам известны.

Ну и в конце запустим тесты и убедимся, что методы контроллера соответствуют этим тестам:

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