web-dev-qa-db-ja.com

モックしたいプロパティが仮想である必要があるのはなぜですか?

私はいくつかの単体テストを行っており、Moqを使用していくつかのプロパティをモックしています。

現在、これはControllerテスト(ASP.NET MVC 3)です。私のコントローラーは、AbstractControllerと呼ばれるabstractコントローラーから派生します。

このコントローラーは、Httpコンテキストに依存しています(テーマ設定、HTTPホストヘッダーに基づくドメイン固有のロジックなどを実行するため)。

これはWebSiteSettingsと呼ばれるプロパティを介して行われます:

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

   // other code
}

プライベートセットに注意してください-アクターがセットアップします。それで、インターフェイスを使用するように変更しました。

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-テストに合格します。

しかし、なぜこれを行う必要があるのか​​理解できません。私は実際にプロパティをオーバーライドしません、私は単にそれがどのように設定されているかをockしています。

私は何かを見逃していますか、これを間違っていますか?

39
RPM1984

Moqおよび他の同様のモックフレームワークは、インターフェイス、抽象メソッド/プロパティ(抽象クラ​​ス)、または仮想クラス/具象クラスのプロパティのみをモックできます。

これは、呼び出しをインターセプトするために、インターフェイスを実装するプロキシを生成するか、これらのオーバーライド可能なメソッドをオーバーライドする派生クラスを作成するためです。

59
aqwert

インターフェイスとラッパークラスを作成しました。例えば.

    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をモックアウトし、後で実行されたサービスを再度チェックするための時間を返すことができます。

4
mkaj

「だから...私がしたことは唯一の方法ですか?」

唯一の方法ではありません-インターフェイスを実装して、それをock笑する方がずっと良いです。その後、実際のメソッドは、選択したとおりに仮想化することもできます。

3
Giles Bradshaw

前に述べたことはすべて真実ですが、プロキシモッキングアプローチ(moqが使用するアプローチなど)が唯一のアプローチではないことを知っておく価値があります。

http://www.typemock.com/ をチェックすると、包括的なソリューションが得られます。これにより、シールドクラス、非仮想メソッドなどの両方をモックできます。かなり強力です。

0
mikus