我的viewmodel中有很多属性,它们中的逻辑非常少但是它们中有RaisePropertyChanged()方法来刷新GUI。即。
private bool _StatesIsSelected;
public bool StatesIsSelected
{
get { return _StatesIsSelected; }
set
{
_StatesIsSelected = value;
RaisePropertyChanged("StatesIsSelected");
}
}
我开始怀疑是否应该通过单元测试确认已调用RaisePropertyChanged()方法。如果我忘了将它放在属性中,GUI就不会刷新,应用程序就会出现错误......所以应该进行单元测试。但是你如何测试呢?
总而言之......我是否因为对这种逻辑进行单元测试而好战?如果我不是激进的......有什么好的方法来测试它?
答案 0 :(得分:2)
无论如何,是的,有一个非常好的方法来做到这一点(不得不做一些小的tweek,所以不能保证代码将编译,但应该使概念清楚):
public static class PropertyChangedTestHelperFactory
{
/// <summary>
/// Factory method for creating <see cref="PropertyChangedTestHelper{TTarget}"/> instances.
/// </summary>
/// <param name="target">
/// The target.
/// </param>
/// <typeparam name="TTarget">
/// The target type.
/// </typeparam>
/// <returns>
/// The <see cref="PropertyChangedTestHelper{TTarget}"/>
/// </returns>
public static PropertyChangedTestHelper<TTarget> CreatePropertyChangedHelper<TTarget>(
this TTarget target)
where TTarget : INotifyPropertyChanged
{
return new PropertyChangedTestHelper<TTarget>(target);
}
}
public sealed class PropertyChangedTestHelper<TTarget> : IDisposable
where TTarget : INotifyPropertyChanged
{
/// <summary>
/// This list contains the expected property names that should occur in property change notifications
/// </summary>
private readonly Queue<string> propertyNames = new Queue<string>();
/// <summary>
/// The target of the helper
/// </summary>
private readonly TTarget target;
/// <summary>
/// Initialises a new instance of the <see cref="StrictPropertyChangedTestHelper{TTarget}"/> class.
/// </summary>
/// <param name="target">The target.</param>
public PropertyChangedTestHelper(TTarget target)
{
this.target = target;
this.target.PropertyChanged += this.HandleTargetPropertyChanged;
}
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
public void Dispose()
{
this.target.PropertyChanged -= this.HandleTargetPropertyChanged;
if (this.propertyNames.Count != 0)
{
Assert.Fail("Property change notification {0} was not raised", this.propertyNames.Peek());
}
}
/// <summary>
/// Sets an expectation that a refresh change notification will be raised.
/// </summary>
public void ExpectRefresh()
{
this.propertyNames.Enqueue(string.Empty);
}
/// <summary>
/// Sets an expectation that a property change notification will be raised.
/// </summary>
/// <typeparam name="TProperty">The type of the property.</typeparam>
/// <param name="propertyExpression">The property expression.</param>
public void Expect<TProperty>(Expression<Func<TTarget, TProperty>> propertyExpression)
{
this.propertyNames.Enqueue(((MemberExpression)propertyExpression.Body).Member.Name);
}
/// <summary>
/// Handles the target property changed event.
/// </summary>
/// <param name="sender">The sender.</param>
/// <param name="e">The <see cref="PropertyChangedEventArgs"/> instance containing the event data.</param>
private void HandleTargetPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (this.propertyNames.Count == 0)
{
Assert.Fail("Unexpected property change notification {0}", e.PropertyName);
}
var expected = this.propertyNames.Dequeue();
var propertyName = (e.PropertyName ?? string.Empty).Trim();
if (propertyName != expected)
{
Assert.Fail("Out of order property change notification, expected '{0}', actual '{1}'", expected, propertyName);
}
}
}
用法:
[TestMethod]
public void StatesIsSelected_RaisesIsValidChangeNotification()
{
// Arrange
var target = new SomeViewModel();
using (var helper = target.CreatePropertyChangedHelper())
{
helper.Expect(item => item.StatesIsSelected);
// Act
target.StatesIsSelected = true;
// Assert
}
}
当帮助者被处置时,期望被询问,如果他们没有按照他们被定义的顺序得到满足,则测试将失败。
我们还有一个弱版本,只需要满足期望,而不是确切地满足它们(即可以引发其他属性更改事件)并且这不依赖于顺序。
仅供参考 - 如果我是你,我会考虑放弃MVVMLight并转向Caliburn.Micro,它是一个不同的联盟。
答案 1 :(得分:0)
您可以轻松测试:
void TestMethod()
{
Container container = new Container();
bool isRaised = false;
container.PropertyChanged += (o,e) => {
if(e.PropertyName == "StatesIsSelected")
isRaised = true;
};
container.StatesIsSelected = true;
Assert.True(isRaised);
}
它认为为ViewModel基类编写测试是有用的,但对于所有引发更改的属性都没有用,这太过极了
答案 2 :(得分:0)
如果您定位.NET Framework >= 4.5
,则可以继承ViewModelBase
并编写帮助方法:
public class ViewModelBaseExtended : ViewModelBase
{
protected void TryRaisePropertyChanged<T>(ref T oldValue, T newValue,
[CallerMemberName] string propertyName = "")
{
if (oldValue == null || !oldValue.Equals(newValue))
{
oldValue = newValue;
RaisePropertyChanged(propertyName);
}
}
}
您的属性代码如下:
private bool _StatesIsSelected;
public bool StatesIsSelected
{
get { return _StatesIsSelected; }
set
{
TryRaisePropertyChanged(ref _StatesIsSelected, value);
}
}
现在你只需要在单元测试中断言属性值。