Интеграционные тесты с ядром asp.net (тест контроллеров без представлений) - PullRequest
1 голос
/ 25 октября 2019

Я пытаюсь настроить тестовый проект для тестирования моих контроллеров с идентификацией и базой данных без необходимости определения представлений.

У меня есть проект модульного тестирования, где я могу протестировать свой контроллер, создав его экземпляр, передавdbContext для конструктора.

 public class EventControllerTests
    {
        private readonly IEventRepository _eventRepository;
        private readonly EventController _controller;
        private readonly AppDbContext dbContext;

        const string cn = "Data Source=(localdb)\\MSSQLLocalDB;Initial Catalog=EventDb;Integrated Security=True;Connect Timeout=30;Encrypt=False;TrustServerCertificate=False;ApplicationIntent=ReadWrite;MultiSubnetFailover=False";

        public EventControllerTests()
        {

            var options = new DbContextOptionsBuilder<EVNTS.Web.Database.AppDbContext>()
                .UseSqlServer(cn).Options;
            dbContext = new EVNTS.Web.Database.AppDbContext(options);
            // Arrange
            _eventRepository = new EventRepository(dbContext);
            _controller = new EVNTS.Web.Controllers.EventController(_eventRepository);
        }

        [Fact]
        public void ActionIndexTest()
        {
            // Act
            var result = _controller.Index(1);

            // Assert
            var model = (Event)result.Model;
            Assert.Equal(1, model.Id);
        }
    }

У меня есть проект интеграционного теста, в котором я использую WebApplicationFactory

 public class BasicTests : IClassFixture<WebApplicationFactory<EVNTS.Startup>>
    {
        private readonly WebApplicationFactory<EVNTS.Startup> _factory;
        private readonly HttpClient _client;

        public BasicTests(WebApplicationFactory<EVNTS.Startup> factory)
        {
            _factory = factory;
            _client = _factory.CreateClient();
        }

        [Theory]
        [InlineData("/")]
        public async Task Get_EndpointsReturnSuccessAndCorrectContentType(string url)
        {

            // Act
            var response = await _client.GetAsync(url);

            // Assert
            response.EnsureSuccessStatusCode(); // Status Code 200-299
            Assert.Equal("text/html; charset=utf-8",
                response.Content.Headers.ContentType.ToString());
        }


        [Fact]

        public async Task TestUserRegistration()
        {
            var s = _factory.Services.GetRequiredService<EVNTS.Web.Repositories.IEventRepository>();
            var url = "/user/register";
            var inputModel = new EVNTS.Web.ViewModels.RegisterModel()
            {
                UserName = "eric",
                Password = "123456",
                ConfirmPassword = "123456"
            };
            var sObj = JsonSerializer.Serialize(inputModel);
            var content = new StringContent(sObj, Encoding.UTF8, "application/json");
            content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
            var response = await _client.PostAsync(url, content);
            var result = response.Content.ReadAsStringAsync();
        }

    }

Проблема заключается в том, что при втором варианте необходимо создавать представленияи мне нужно использовать библиотеку, такую ​​как AngleSharp, чтобы проверить результаты.

Я бы хотел что-то среднее между тем, где я мог бы напрямую вызвать конструктор и проверить представление результатов, но с DI, внедряющим UserManager и dbContext для меня.

есть идеи?

Приветствия

Вот контроллер:

public class UserController : Controller
    {
        private readonly UserManager<User> _userManager;
        public UserController(UserManager<User> userManager)
        {
            _userManager = userManager;
        }

        [HttpPost]
        public async Task<IActionResult> Register([FromBody] RegisterModel model)
        {
            IdentityResult? result=null;

            if (ModelState.IsValid)
            {
                var user = await _userManager.FindByNameAsync(model.UserName);
                if (user == null)
                {
                    user = new User
                    {
                        Id = Guid.NewGuid(),
                        UserName = model.UserName,
                    };
                    result = await _userManager.CreateAsync(user, model.Password);
                }
            }
            return View(result);
        }
    }

Ответы [ 2 ]

0 голосов
/ 26 октября 2019

Я не считаю себя авторитетом в том, как выполнять модульное тестирование, но, поскольку раздел комментариев ограничен в символах, я буду писать свои комментарии здесь.

Обычно, когда вы попадаете в ситуацию, когдаТрудно придумать хороший модульный тест (я не буду определять «хороший» здесь чаще), потому что есть некоторые проблемы со структурой проекта / дизайном кода, а не реальные ограничения самого модульного тестирования. (опять же, не то, чтобы модульное тестирование не имело своих ограничений, но я думаю, что это не так).

На основании вышеизложенного я попросил вас включить код действия, чтобы мы могли проверить, что именноВы пытаетесь проверить, и почему это так сложно.

Вот часть моего комментария, основанная на мнениях, но я оставляю это на ваше усмотрение, если вы захотите принять это или оставить.

Это не правило, но хорошее практическое правило заключается в том, что контроллер должен содержать очень мало бизнес-логики, что означает, что модультестирование контроллера должно в основном проверять различные пути, по которым может идти запрос, как только он попадает в контроллер.

Как правило, вы хотите что-то вроде этого:

if (!ModelState.IsValid)
{
  return BadRequest(ModelState);
}

var user = await _userManager.FindByNameAsync(model.UserName);
...
return View(result);

, который затем вы можете объединитьпроверить что-то вроде этого:

public async Task Register_Returns_BadRequest_On_Invalid_Model()
{
  var testUsername = "TestUsername";
  var mockUserManager = new Mock<IUserManager>();
  mockUserManager.Setup(m => m.FindByNameAsync(testUsername))
    .Returns(Task.FromResult(**Not sure about this part**))
  var controller = new RegisterController(mockUserManager.Object);

  var result = await controller.Register(model: null);

  var actionResult = Assert.IsType<ActionResult<IdentityResult>>(result);
  Assert.IsType<BadRequestObjectResult>(actionResult.Result);
}

Для счастливого пути вы хотите только проверить, что на действительном ModelState результат имеет тип ActionResult>

Что такоемоя идея:

  • Когда вы тестируете блок контроллером, вас не должны беспокоить реальные данные, это ответственность других частей приложения
  • Тест блока контроллера должен быть простымпросто, в большинстве случаев вам следует тестировать только эти два случая - неверные данные возвращают своего рода BadRequest, действительные данные возвращают ожидаемый ответ
  • Если вы обнаружите, что большую часть времени вы смеетесь над объектами, это яснопризнак того, что вам нужен дополнительный уровень абстракции.

В вашем случае, чтобы улучшить мой кодструктурированный и более легкий для тестирования, я бы сделал следующее:

  • Первый тест на недопустимый ModelState - вы не хотите продолжать, если ModelState недопустим, и это также должно охватываться модульным тестом.
  • Менеджеры должны быть более высокого уровня абстракции. Такие методы, как FindByNameAsync и CreateAsync больше подходят для уровня доступа к данным. В случае этого действия ваш UserManager может иметь метод, подобный Register, поэтому действие вашего контроллера выглядит следующим образом:

    if (! ModelState.IsValid)

    {

    return BadRequest()
    

    }

    var result = _userManager.Register (model.UserName);

    return View (результат);

  • сейчасВы можете удалить методы Find и Create из контроллера и создать UserRepository, где я использую эти методы и где вы можете проверить их изолированно.

В этой настройке у вас есть этиАбстракция Контроллер -> Менеджер -> Хранилище. Теперь вы пытаетесь протестировать три из них в одном методе, который, на мой взгляд, вызывает проблемы.

Кроме того, просто потому, что я считаю это немного более аккуратным, обычно вы используете слой Service иесли структура слишком сложна, вы добавляете слой менеджера, чтобы он стал Controller -> Manager -> Service -> Repository. В вашем случае я не уверен, что вам нужна эта сложность, поэтому, возможно, ради лучшего именования, переименуйте UserManager в UserService, чтобы ваш поток кода был Controller -> Service -> Repository.

Кроме того, последний совет. Тестирование контроллера всегда было противоречивым, так что не беспокойтесь, если вы не покрываете свой контроллер юнит-тестами так же, как другие части кода. Это несколько ожидаемо, и я хотел сказать в этом посте, главным образом, что проблема не в том, как тестировать, а в том, что тестируемый код сам по себе, что, на мой взгляд, может быть улучшено, как я показал выше. Да, мое предложение также не идеально, но оно создает меньшие куски кода, которые инкапсулированы и не имеют такого количества зависимостей, что в конечном итоге облегчает их тестирование. И, конечно, это не замена тестов интеграции.

Надеюсь, это дало вам пищу для размышлений.

0 голосов
/ 26 октября 2019

Между ними нет. Первый пример - это модульный тест, а второй - интеграционный тест. Если вы хотите просто посмотреть на объект результата действия, тогда вы будете использовать методологию модульного теста, и вам нужно будет смоделировать ваши зависимости. В противном случае вы бы использовали подход интеграционного тестирования, и вам пришлось бы иметь дело с фактическим смоделированным ответом сервера.

Для чего стоит, действия контроллера должны быть проверены интеграцией, так как они по своей природе зависят от числакомпонентов собираются вместе, поэтому вы должны следовать второму подходу, при необходимости анализируя HTML-ответ.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...