Почему свойство, которое я хочу имитировать, должно быть виртуальным?

#c# #asp.net-mvc #unit-testing #controller #moq

#c# #asp.net-mvc #модульное тестирование #контроллер #moq

Вопрос:

Я провожу некоторое модульное тестирование и издеваюсь над некоторыми свойствами, используя Moq.

Итак, это тест контроллера (ASP.NET MVC 3). Мои контроллеры являются производными от абстрактного контроллера, называемого AbstractController.

Этот контроллер зависит от контекста Http (для того, чтобы выполнять такие вещи, как тематизация, специфичная для домена логика, основанная на заголовках HTTP HOST и т.д.).

Это делается с помощью свойства, называемого WebSiteSettings:

 public abstract class AbstractController : Controller
{
   public WebSiteSettings WebSiteSettings { get; private set; }

   // other code
}
  

Обратите внимание на частный набор — ctor устанавливает его. Итак, я изменил его на used an interface, и это то, что я смоделировал:

 public IWebSiteSettings WebSiteSettings { get; private set; }
  

Затем я создал «FakeWebSiteSettings», который имитирует контекст Http, чтобы он мог читать заголовки HTTP.

Проблема в том, что когда я запускаю тест, я получаю NotSupportedException:

Недопустимая настройка на невиртуальном (переопределяемом в VB) элементе: x => x.WebSiteSettings

Вот соответствующий код для создания макета:

 var mockWebSiteSettings = new Mock<FakeWebSiteSettings>();
var mockController = new Mock<MyController>(SomeRepository);
mockController.Setup(x => x.WebSiteSettings).Returns(mockWebSiteSettings.Object);

_controller = mockController.Object;

var httpContextBase = MvcMockHelpers.FakeHttpContext();
httpContextBase.Setup(x => x.Request.ServerVariables).Returns(new NameValueCollection
    {
        {"HTTP_HOST","localhost.www.mydomain.com"}, 
});
_controller.SetFakeControllerContext(httpContextBase.Object);
  

Если я сделаю WebsiteSettings свойство виртуальным — тест пройден.

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

Я что-то упускаю или делаю это неправильно?

Ответ №1:

Moq и другие подобные фреймворки-имитаторы могут имитировать только интерфейсы, абстрактные методы / свойства (в абстрактных классах) или виртуальные методы / свойства в конкретных классах.

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

Комментарии:

1. Единственный способ с Moq. Другие, такие как TypeMock Isolator, Moles могут перехватывать эти методы / свойства

Ответ №2:

Я создал интерфейс и класс-оболочку. например

     public interface IWebClient
    {
        string DownloadString(string url);
    }

    public class WebClient : IWebClient
    {
        private readonly System.Net.WebClient _webClient = new System.Net.WebClient();

        public string DownloadString(string url)
        {
            return _webClient.DownloadString(url);
        }
    }
  

и затем в ваших модульных тестах просто имитируйте интерфейс:

         var mockWebClient = new Mock<IWebClient>();
  

Очевидно, вам может потребоваться включить больше свойств / методов. Но это делает свое дело.

Еще один полезный прием для других проблем с имитацией, таких как изменение текущего времени даты (я всегда использую время даты UTC):

 public interface IDateTimeUtcNowProvider
{
    DateTime UtcNow { get; } 
}

public class DateTimeUtcNowProvider : IDateTimeUtcNowProvider
{
    public DateTime UtcNow { get { return DateTime.UtcNow; } }
}
  

например, если у вас есть служба, которая запускается каждые x минут, вы можете просто смоделировать IDateTimeProvider и вернуть более позднее время, чтобы проверить, что служба снова запущена … или что-то еще.

Комментарии:

1. Для .NET 5 ISystemClock

Ответ №3:

«Итак….то, что я сделал, — единственный способ?»

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

Комментарии:

1. Меня интересует этот ответ, но я его не понимаю! Джайлс, не мог бы ты пояснить, возможно, используя оригинальный пример RPM1984?

Ответ №4:

Хотя все, что было сказано ранее, верно, стоит знать, что подход с имитацией прокси (подобный тому, который использует moq) не является единственно возможным.

Проверьте http://www.typemock.com для комплексного решения, которое позволяет вам имитировать оба закрытых класса, невиртуальные методы и т.д. Довольно мощный.