web-dev-qa-db-ja.com

Moqを使用して同じクラスの仮想メソッドをオーバーライドする

Moqを使用してサービスクラスの単体テストを行っていますが、サービスメソッドが同じクラスの別のサービスメソッドを呼び出す状況をテストする方法に固執しています。呼び出されるメソッドを仮想に設定しようとしましたが、Moqで何をすべきかがわかりませんでした。例えば:

public class RenewalService : IRenewalService
{
    //we've already tested this
    public virtual DateTime? GetNextRenewalDate(Guid clientId)
    {
        DateTime? nextRenewalDate = null;
        //...<snip> a ton of already tested stuff...

        return nextRenewalDate;
    }

    //but want to test this without needing to mock all 
    //the methods called in the GetNextRenewalDate method
    public bool IsLastRenewalOfYear(Renewal renewal)
    {
        DateTime? nextRenewalDate = GetNextRenewalDate(renewal.Client.Id);
        if (nextRenewalDate == null)
            throw new Exceptions.DataIntegrityException("No scheduled renewal date, cannot determine if last renewal of year");
        if (nextRenewalDate.Value.Year != renewal.RenewDate.Year)
            return true;
        return false;
    }
}

上記の例では、GetNextRenewalDateメソッドはかなり複雑であり、すでに単体テストを行っています。ただし、GetNextRenewalDateに必要なすべてをモックする必要なしに、より単純なIsLastRenewalOfYearをテストしたいと思います。基本的には、GetNextRenewalDateをモックしたいだけです。

GetNextRenewalDateをオーバーライドする新しいクラスを作成して新しいクラスをテストできることはわかっていますが、Moqを利用してこれを簡単にする方法はありますか?

29
Andrew

このシナリオでは、おそらく部分的なモックを使用できますが、すべてのメソッドは仮想である必要があります。

    var mock = new Moq.Mock<RenewalService>();
    mock.Setup(m => m.GetNextRenewalDate(It.IsAny<Guid>())).Returns(null);
    mock.CallBase = true;
    var results = mock.Object.IsLastRenewalOfYear(...);
45
Beep beep
var mock = new Moq.Mock<RenewalService> { CallBase = true };
mock.Setup(m => m.GetNextRenewalDate(It.IsAny<Guid>())).Returns(null);
var results = mock.Object.IsLastRenewalOfYear(...);
3
abatishchev

編集:これがアンドリューの場合の正しい答えではないことに同意します。コメントスレッドのためにこの答えをここに残したいと思います。これ以上反対票を投じないでください:)

編集前:

通常、モックオブジェクトフレームワークは、単一クラスのシナリオを単純化するようには設計されていません。単一クラスをテストできるように、コードを分離するように設計されています。

これを解決するためにモックオブジェクトフレームワークを使用しようとすると、フレームワークは派生クラスを作成し、そのメソッドをオーバーロードします。唯一の違いは、派生クラス定義を作成する必要がないため、5行ではなく約3行で実行できることです。

モックオブジェクトを使用してこの動作を分離したい場合は、このクラスを少し分割する必要があります。 GetNextRenewalDateロジックはRenewalServiceオブジェクトの外部に存在する可能性があります。

この問題が発生しているという事実は、まだ発見されていない、より単純な、またはよりきめ細かい設計があることを示している可能性があります。 「manager」や「service」のような名前の、少し具体的でないクラスを見つけることは、多くの場合、設計をより小さなクラスに分割し、そのクラスからより良い再利用性と保守性を得ることができるというヒントです。