如何使用 Moq 验证是否使用预期对象和 属性 名称调用了 PropertyChanged?

How to use Moq to Verify that PropertyChanged is invoked with the expected object and property name?

使用 NUnit 和 Moq,我想用 Moq 替换以下测试中的 PropertyChanged 处理程序代码,如果这样可以使测试更短更清晰。我目前不清楚如何在 Moq 中执行此操作,验证是否为 IsDirty 调用了一次 PropertyChanged,为 LocalDatabaseFilePath 调用了一次,每次都使用预期的对象(代码中的 o)。有人可以建议如何使用最小起订量做到这一点吗?

        [Test]
        [Category("Fast Tests")]
        [Category("PropertyChanged Events")]
        public void FactoryResetCommand_AllPropertiesChangedInViewModel_PropertyChangedEventsFiredAsExpected()
        {
            // Arrange

            string expectedLocalDatabaseFilePath = "eldfp";
            string otherLocalDatabaseFilePath = "other" + expectedLocalDatabaseFilePath;

            Mock<IDataStoreSettingsDataModel> stubDataModel = new Mock<IDataStoreSettingsDataModel>();
            stubDataModel.Setup(x => x.LocalDatabaseFilePath).Returns(expectedLocalDatabaseFilePath);

            IDataStoreSettingsViewModel sutViewModel = new DataStoreSettingsViewModel(
                stubDataModel.Object,
                ReportExceptionAsync);
            sutViewModel.LocalDatabaseFilePath = otherLocalDatabaseFilePath;

            sutViewModel.IsDirty = false;

            // I'd like to replace the following by Moq if shorter/clearer

            int propertyChangedCountIsDirty = 0;
            int propertyChangedCountLocalDatabaseFilePath = 0;
            object? objIsDirty = null;
            object? objLocalDatabaseFilePath = null;

            sutViewModel.PropertyChanged += ((o, e) =>
            {
                switch (e?.PropertyName)
                {
                    case nameof(DataStoreSettingsViewModel.IsDirty):
                        objIsDirty = o;
                        ++propertyChangedCountIsDirty;
                        break;
                    case nameof(DataStoreSettingsViewModel.LocalDatabaseFilePath):
                        objLocalDatabaseFilePath = o;
                        ++propertyChangedCountLocalDatabaseFilePath;
                        break;
                }
            });

            // I'd like to replace the above by Moq if shorter/clearer

            // Act

            if (sutViewModel.FactoryResetCommand.CanExecute(null))
                sutViewModel.FactoryResetCommand.Execute(null);

            // Assert

            Assert.AreEqual(1, propertyChangedCountIsDirty);
            Assert.AreEqual(1, propertyChangedCountLocalDatabaseFilePath);
            Assert.AreSame(sutViewModel, objIsDirty);
            Assert.AreSame(sutViewModel, objLocalDatabaseFilePath);
        }

自己解决了。

添加如下界面后:

    public interface IPropertyChangedEventHandler
    {
        void PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e);
    }

使用 Moq 的测试如下所示:

        [Test]
        [Category("Fast Tests")]
        [Category("PropertyChanged Events")]
        public void FactoryResetCommand_AllPropertiesChangedInViewModel_PropertyChangedEventsFiredAsExpected()
        {
            // Arrange

            string originalLocalDatabaseFilePath = "oldfp";
            string otherLocalDatabaseFilePath = "other" + originalLocalDatabaseFilePath;

            Mock<IPropertyChangedEventHandler> mockPropertyChangedEventHandler = new Mock<IPropertyChangedEventHandler>();

            Mock<IDataStoreSettingsDataModel> stubDataModel = new Mock<IDataStoreSettingsDataModel>();
            stubDataModel.Setup(x => x.LocalDatabaseFilePath).Returns(originalLocalDatabaseFilePath);

            IDataStoreSettingsViewModel sutViewModel = new DataStoreSettingsViewModel(
                stubDataModel.Object,
                ReportExceptionAsync);
            sutViewModel.LocalDatabaseFilePath = otherLocalDatabaseFilePath;

            sutViewModel.IsDirty = false;

            sutViewModel.PropertyChanged += mockPropertyChangedEventHandler.Object.PropertyChanged;

            // Act

            if (sutViewModel.FactoryResetCommand.CanExecute(null))
                sutViewModel.FactoryResetCommand.Execute(null);

            // Assert

            mockPropertyChangedEventHandler.Verify(x => x.PropertyChanged(sutViewModel,
                It.Is<System.ComponentModel.PropertyChangedEventArgs>(e => e.PropertyName == nameof(DataStoreSettingsViewModel.IsDirty))), 
                Times.Once);
            mockPropertyChangedEventHandler.Verify(x => x.PropertyChanged(sutViewModel,
                It.Is<System.ComponentModel.PropertyChangedEventArgs>(e => e.PropertyName == nameof(DataStoreSettingsViewModel.LocalDatabaseFilePath))), 
                Times.Once);
        }