web-dev-qa-db-ja.com

SetupGetとSetupSetを使用してプロパティをモックする-これは機能しますが、なぜですか?

Moqを使用して、インターフェイスISessionDataのプロパティ_Report TheReport { get; set; }_をモックして、このプロパティに設定される値を検査できるようにします。

これを実現するには、次のようにSetupGetSetupSetを使用します。

_// class-level fields
protected Report _sessionReport;
protected Mock<ISessionData> SessionData { get; private set; }
_

そして私のセットアップ方法では...

_SessionData = new Mock<ISessionData>();

SessionData
    .SetupSet(s => s.TheReport = It.IsAny<Report>())
    .Callback<RDLDesigner.Common.Report>(r =>
    {
        _sessionReport = r;
        SessionData.SetupGet(s => s.TheReport).Returns(_sessionReport);
    });
_

私は StackOverflow でこのアプローチを見つけましたが、それは機能しますが、理由がわかりません。 SetupGetコールバックの外でSetupSetを呼び出すことを期待していました。

このアプローチがどのようにそしてなぜ機能するのか、そしてそれがこのタイプのプロパティをモックする最も適切な方法であるかどうか誰もが説明できますか?

編集する

SessionData.SetupProperty(s => s.TheReport);の使用も私のシナリオで機能しますが、私の元のアプローチがどのように、なぜ機能したのかについての説明に興味があります。

23
Richard Ev

SetupGetの呼び出しでコールバックが使用される理由は、_sessionReport参照が値によって渡されるためです。これは、Setメソッドへの後続の呼び出しでは、getメソッドによって返される値が更新されないことを意味します。

何が起こっているのかをより明確に確認するため。 Mockを次のように設定した場合:-

SessionData.SetupSet(s => s.Report = It.IsAny<Report>());
SessionData.SetupGet(s => s.Report).Returns(_report);

擬似コードでは、Mocked実装は次のようになります。

public Report Report {
    set { }
    get { 
       // Copy of the value of the _report reference field in your test class
       return _reportCopy; 
    }  
}

したがって、このようなことをしてもうまくいきません:-

 ISessionData session = SessionData.Object
 Report report = new Report();
 session.Report = report;
 session.Report.ShouldEqual(report); //Fails
 _report.ShouldEqual(report); // Fails

明らかに、Setメソッドにいくつかの動作を追加する必要があるため、Mockを次のように設定します。

SessionData.SetupSet(s => s.Report = It.IsAny<Report>())
           .Callback(s => _report = s);
SessionData.SetupGet(s => s.Report).Returns(_report);

これにより、モック実装が少し似たものになります。

public Report Report {
    set {
       // Invokes delegate that sets the field on test class
    }
    get { 
       // Copy of the original value of the _report reference field
       // in your test class
       return _reportCopy; 
    }  
}

ただし、これにより次の問題が発生します。

  ISessionData session = SessionData.Object
  Report report = new Report();
  session.Report = report;
  _report.ShouldEqual(report); // Passes
  session.Report.ShouldEqual(report); //Fails!

本質的には、プロパティの "get"メソッドは、_Getが参照している元のオブジェクトへの参照を返しますが、参照は値によってSetupGetメソッドに渡されたためです。

したがって、元のコードにつながるセッターが呼び出されるたびに、レポートゲッターが返す値を更新する必要があります。

SessionData
   .SetupSet(s => s.TheReport = It.IsAny<Report>())
   .Callback<RDLDesigner.Common.Report>(r => {
        _sessionReport = r;
        SessionData.SetupGet(s => s.TheReport).Returns(_sessionReport);
   });

これにより、Getメソッドによって返された値が、setメソッドの以前の呼び出しと常に同期されます。そして、(機能的に)次のように動作するものにつながります:-

public Report Report {
    set {
       // Sets the field on the test class
       _reportCopy = value;
    }
    get { 
       // Copy of the value of the _report reference field in your test class
       return _reportCopy; 
    }  
}
32
John Foster

SetupGetをSetupSet実装内に配置するのは、非常に/過度に複雑に思えます。

.Returnsがデリゲートを返すようにすると、参照のコピーを返すだけでなく、毎回評価されます。

このようなものは、見た目がはるかに簡単です(ゲッターを再定義し続ける必要がないため、パフォーマンスが向上します)。

SessionData
   .SetupSet(s => s.TheReport = It.IsAny<Report>())
   .Callback<RDLDesigner.Common.Report>(r => _sessionReport = r);    
SessionData
   .SetupGet(s => s.TheReport).Returns(() => _sessionReport);
11
stoj