Подтвердить что ты не робот

Издевательствовать свойство с помощью SetupGet и SetupSet - это работает, но почему?

Используя Moq, я издеваюсь над свойством Report TheReport { get; set; } на интерфейсе ISessionData, чтобы проверить значение, которое устанавливается в этом свойстве.

Для этого я использую SetupGet и SetupSet следующим образом:

// 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);
    });

Я нашел этот подход на qaru.site/info/255130/..., и он работает, но я не понимаю, почему. Я ожидал получить вызов SetupGet за пределами обратного вызова SetupSet.

Может ли кто-нибудь объяснить, как и почему работает этот подход, и если это наиболее подходящий способ издеваться над свойством этого типа?

Изменить

Использование SessionData.SetupProperty(s => s.TheReport); также работает в моем сценарии, но меня все еще интересуют любые объяснения того, как и почему мой оригинальный подход работал.

4b9b3361

Ответ 1

Причина, по которой обратный вызов используется в вызове SetupGet, заключается в том, что ссылка _sessionReport передается по значению, это означает, что последующий вызов метода Set не будет обновлять значение, возвращаемое методом get.

Чтобы узнать, что происходит более четко. Если вы настроили свой макет следующим образом: -

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

Это приводит к реализации Mocked, немного похожей на

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" в свойстве по-прежнему возвращает ссылку на исходный объект, на который указывал _report, поскольку ссылка была передана по значению методу 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; 
    }  
}

Ответ 2

Кажется ужасно/чрезмерно сложным, чтобы установить 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);