Как проверить методы контроллера в C # - PullRequest
0 голосов
/ 06 сентября 2018

Я недавно начал модульное тестирование кода C #, но я никогда раньше не тестировал классы контроллеров. У меня есть следующий класс, мне нужно написать контрольные примеры для этого.

namespace nH.MasterData.API.Controllers
{
    [Produces("application/json")]
    [Route("api/AType")]
    public class ATypeController : Controller
    {
        private readonly IProcessor _domain;
        private readonly ILogger _logger;
        private const string Version = "VERSION_1";
        private const string Model = "model";
        private const string Entity = "AType";

        public ATypeController(IProxy proxy, ILogger<ATypeController> logger)
        {
            _logger = logger;
            var proxy = proxy.GetProxy();
            _domain = new InstitutionAddressProcessor(proxy);
        }

        [HttpGet]
        public OkObjectResult Get()
        {
            try
            {
                return Ok(_domain.GetATypesMembers(Model, Version, Entity, MemberType.Leaf, null));
            }
            catch (Exception ex)
            {
                _logger.LogError(ex.Message + " " + ex.StackTrace);
                return new OkObjectResult(BadRequest());
            }
        }

    }
}

Здесь _domain.GetATypesMembers() отсутствует в моем проекте, и я не создаю объект _domain явно (он создан внутри конструктора). Как получить контроль над объектом, который создается в конструкторе? Чтобы я мог высмеивать ответ при вызове, фактически не совершая реальный вызов.

Обычно я setup высмеиваю объект как wcfMockService.Setup(x => x.GetATypesMembers(...).ReturnAsync(response); Но как мне написать для этого?

Я ценю любые предложения.

Edit1: создание объектов внутри конструктора - плохая практика. Но это устаревший код, который используется во многих местах. Поэтому я просто пытаюсь написать тесты, поскольку код существует.

Ответы [ 4 ]

0 голосов
/ 06 сентября 2018

Вы ничего не получите бесплатно.Если вы хотите быть в состоянии проверить логику, вы должны отделить вещи.Если вы не отсоедините их, вы не сможете должным образом протестировать, потому что вы тестируете что-то с зависимостью, которую вы сами не можете проверить.Таким образом, «Unit» не существует.

Сказав, что это может быть устаревший код, но приличный инструмент, такой как resharper, с которым я совсем не связан, может помочь вам с рефакторингом.После того, как вы внедрили услугу, вы можете выполнить надлежащий модульный тест.

0 голосов
/ 06 сентября 2018

Я буду использовать Внедрение зависимости в этом случае. Его форма Инверсионный контроль . Вместо передачи прокси и _domain в методе я передам его в качестве аргумента (прокси передается, но я передам _domain в качестве аргумента). _Домен будет создан где-то в Main like method или можно использовать ['AutoFac']. Тогда становится легко создавать юнит-тесты.

Вот полезная ссылка на тестирование контроллеров с использованием Внедрение зависимостей

Как указывалось выше, существует жесткая связь, которая затрудняет юнит-тестирование.

0 голосов
/ 06 сентября 2018

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

Что-то вроде этого должно позволить вам установить поле после того, как вы построили свой sut.

// Arrange
var sut = SutProvider.GetATypeController(); // A system under test factory.

var mock = new Mock<IProcessor>();
// ... mock setup ..

typeof(ATypeController)
    .GetField("_domain", BindingFlags.Instance | BindingFlags.NonPublic)
    .SetValue(sut, mock.Object);

// Act
var result = sut.Get();

// Assert
// ... assertions of result

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

SutProvider - простая фабрика, реализация может выглядеть так:

public class SutProvider 
{
    public static ATypeController GetATypeController() => new ATypeController(GetProxy(), GetATypeControllerLogger());

    public static IProxy GetProxy() {
        // Either return a valid IProxy, or set up a mock that can return a result from the GetProxy method that is valid enough to withstand InstitutionAddressProcessor's constructor.
    }

    public static ILogger<ATypeController> GetATypeControllerLogger() => new Mock<ILogger<ATypeController>>().Object;
}

Скорее всего, вам понадобится немного погрузиться в конструктор InstitutionAddressProcessor, чтобы найти лучший способ передать ему достаточно действительный прокси.

0 голосов
/ 06 сентября 2018

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

Этот контроллер должен быть реорганизован, чтобы зависеть от абстракций, а не от конкреций.

namespace nH.MasterData.API.Controllers {
    [Produces("application/json")]
    [Route("api/AType")]
    public class ATypeController : Controller {
        private readonly IProcessor _domain;
        private readonly ILogger _logger;
        private const string Version = "VERSION_1";
        private const string Model = "model";
        private const string Entity = "AType";

        public ATypeController(IProcessor domain, ILogger<ATypeController> logger) {
            _logger = logger;            
            _domain = domain;
        }

        [HttpGet]
        public IActionResult Get() {
            try {
                return Ok(_domain.GetATypesMembers(Model, Version, Entity, MemberType.Leaf, null));
            } catch (Exception ex) {
                _logger.LogError(ex.Message + " " + ex.StackTrace);
                return BadRequest();
            }
        }
    }
}

Теперь при тестировании можно смоделировать зависимости при необходимости

var processor = new Mock<IProcessor>();
var logger = new Mock<ILogger<ATypeController>>();
var controller = new ATypeController(processor.Object, logger.Object); 

//...setup mocks

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

Это может быть выполнено в корне композиции.

services.AddScoped<IProcessor>(_ => {                
    var proxy = _.GetService<IProxy>();
    return new InstitutionAddressProcessor(proxy.GetProxy());
});
...