Насмешка над свойством с помощью SetupGet и SetupSet - это работает, но почему? - PullRequest
21 голосов
/ 24 июня 2010

Используя 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);
    });

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

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

Редактировать

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

Ответы [ 2 ]

30 голосов
/ 07 июля 2010

Причина, по которой обратный вызов используется в вызове 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; 
    }  
}
10 голосов
/ 07 апреля 2011

Кажется ужасно / слишком сложно поместить 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);
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...