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

Я провожу некоторые юнит-тесты и проверяю некоторые свойства, используя 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 свойство виртуальное - тест проходит.

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

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

4 ответа

Решение

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

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

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

    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>();

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

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

public interface IDateTimeUtcNowProvider
{
    DateTime UtcNow { get; } 
}

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

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

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

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

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

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

Другие вопросы по тегам