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

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

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

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

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

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

   // other code
}

Обратите внимание на частный набор - ctor устанавливает его. Итак, я изменил его на использование интерфейса, и то, что я высмеивал:

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 virtual - тест проходит.

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

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

Ответ 1

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

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

Ответ 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 и возвращать время, которое позже проверяет, что служба снова запущена... или что-то еще.

Ответ 3

"Итак... что я сделал, это единственный способ?"

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

Ответ 4

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

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