MVVM:异步事件处理程序

时间:2015-11-06 09:35:04

标签: c# unit-testing asynchronous mvvm async-await

我有一个带有异步任务的viewModel。我现在不知道如何测试它。

public class MyViewModel : BindableBase
{
    public MyViewModel()
    {
        this.PropertyChanged += MyViewModel_PropertyChanged;
    }

    private void MyViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        Action action = async () => await DoSomething();
        action();
    }

    public const string BeforeKey = "before";
    public const string AfterKey = "After";

    public string Status { get; private set; } = BeforeKey;

    public async Task DoSomething()
    {
        await Task.Delay(3000);
        Status = AfterKey;
    }

    string bindagleProp;
    public string BindagleProp
    {
        get { return bindagleProp; }
        set { SetProperty(ref bindagleProp, value); }
    }
}

这是我的测试:

[TestMethod]
public async Task TestMyViewModel()
{
    MyViewModel viewModel = new MyViewModel();
    Assert.AreEqual(viewModel.Status, MyViewModel.BeforeKey, "before check");

    viewModel.BindagleProp = "abc";
    Assert.AreEqual(viewModel.Status, MyViewModel.AfterKey, "after check");
}

测试失败,因为它没有等到完成任务。 我不想在单元测试中使用Task.Delay,因为它不安全。 DoSomething方法的持续时间可能不明。

感谢您的帮助。

编辑:

实际上,该问题并非针对MVVM,而是针对任何异步事件处理程序。 例如:

// class with some logic, can be UI or whatever.
public class MyClassA
{
    Size size;

    public Size Size
    {
        get { return size; }
        set
        {
            size = value;
            SizeChanged?.Invoke(this, EventArgs.Empty);
        }
    }

    public event EventHandler SizeChanged;
}

// this class uses the MyClassA class.
public class MyCunsomerClass
{
    readonly MyClassA myClassA = new MyClassA();

    public MyCunsomerClass()
    {
        myClassA.SizeChanged += MyClassA_SizeChanged;
    }

    public string Status { get; private set; } = "BEFORE";

    private async void MyClassA_SizeChanged(object sender, EventArgs e)
    {
        await LongRunningTaskAsync();
        Status = "AFTER";
    }

    public async Task LongRunningTaskAsync()
    {
        await Task.Delay(3000);
        ///await XYZ....;
    }

    public void SetSize()
    {
        myClassA.Size = new Size(20, 30);
    }
}

现在,我想测试一下:

    [TestMethod]
    public void TestMyClass()
    {
        var cunsomerClass = new MyCunsomerClass();
        cunsomerClass.SetSize();
        Assert.AreEqual(cunsomerClass.Status, "AFTER");
    }

测试失败。

2 个答案:

答案 0 :(得分:2)

好的首先,我会将工作人员移到另一个类并创建一个接口。因此,当我运行测试时,我可以注入另一个工人!

public class MyViewModel : BindableBase
{
    private IWorker _worker;

    private readonly DataHolder _data = new DataHolder(){Test = DataHolder.BeforeKey};
    public string Status { get { return _data.Status; } }

    public MyViewModel(IWorker worker = null)
    {
        _worker = worker;
        if (_worker == null)
        {
            _worker = new Worker();
        }

        this.PropertyChanged += MyViewModel_PropertyChanged;
    }

    private void MyViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
    {

        Action action = async () => await _worker.DoSomething(_data);
        action();
    }


    string bindagleProp;
    public string BindagleProp
    {
        get { return bindagleProp; }
        set { SetProperty(ref bindagleProp, value); }
    }
}

public class DataHolder
{
    public const string BeforeKey = "before";
    public const string AfterKey = "After";

    public string Status;
}

public interface IWorker
{
    Task DoSomething(DataHolder data);
}

public class Worker : IWorker
{
    public async Task DoSomething(DataHolder data)
    {
        await Task.Delay(3000);
        data.Status = DataHolder.AfterKey;
    }
}

现在注入代码看起来像:

[TestMethod]
public async Task TestMyViewModel()
{
    TestWorker w = new TestWorker();

    MyViewModel viewModel = new MyViewModel(w);
    Assert.AreEqual(viewModel.Status, DataHolder.BeforeKey, "before check");

    viewModel.BindagleProp = "abc";
    Assert.AreEqual(viewModel.Status, DataHolder.AfterKey, "after check");
}

public class TestWorker : IWorker
{
    public Task DoSomething(DataHolder data)
    {
        data.Status = DataHolder.BeforeKey;
        return null; //you maybe should return something else here...
    }
}

答案 1 :(得分:1)

我问过Stehphen Cleary [着名的异步教授],他回答我:

  

如果通过"异步事件处理程序"你的意思是一个async void事件处理程序,   那么不,那些是不可测试的。但是,它们通常在UI中很有用   应用。所以我通常最终做的就是让我所有的异步   void方法恰好是一行长。他们都看起来像这样:

async void SomeEventHandler(object sender, EventArgsOrWhatever args)
{
     await SomeEventHandlerAsync(sender, args);
}

async Task SomeEventHandlerAsync(object sender, EventArgsOrWhatever args)
{
      ... // Actual handling logic
}
  

然后async Task版本是单元可测试的,可组合的等等   async void处理程序不是,但由于它不再可以接受   有任何真正的逻辑。

谢谢斯蒂芬!你的想法很棒!