如何单元测试执行异步方法的RelayCommand?

时间:2015-03-18 21:24:39

标签: c# unit-testing asynchronous mvvm-light .net-4.5.2

由于没有RelayCommandAsync(至少不是我所知道的),如何测试这种情况。例如:

public RelayCommand LoadJobCommand
{
    get
    {
        return this.loadJobCommand ?? (
            this.loadJobCommand =
            new RelayCommand(
                this.ExecuteLoadJobCommandAsync));
    }
}

private async void ExecuteLoadJobCommandAsync()
{
   await GetData(...);
}

测试:

vm.LoadJobCommand.Execute()

Assert.IsTrue(vm.Jobs.Count > 0)

3 个答案:

答案 0 :(得分:1)

我没有使用async / await,但我过去遇到过类似的问题。我所处的情况是本身内部称为Task.Run(的方法,单元测试验证ViewModel使用正确的参数以正确的次数调用服务。

我们解决这个问题的方式是我们使用了ManualResetEventSlim来调用服务的Mock,然后单元测试等待在继续之前调用该重置事件。

[TestMethod]
public void EXAMPLE()
{
    using (var container = new UnityAutoMoqContainer())
    {
        //(SNIP)

        var serviceMock = container.GetMock<ITreatmentPlanService>();

        var resetEvent = new ManualResetEventSlim();

        serviceMock.Setup(x=>x.GetSinglePatientViewTable(dateWindow, currentPatient, false))
            .Returns(() =>
            {
                resetEvent.Set();
                return new ObservableCollection<SinglePatientViewDataRow>();
            });

        var viewModel = container.Resolve<SinglePatientViewModel>();

        //(SNIP)

        viewModel.PatientsHadTPClosed(guids, Guid.NewGuid());

        waited = resetEvent.Wait(timeout);
        if(!waited)
            Assert.Fail("GetSinglePatientViewTable was not called within the timeout of {0} ms", timeout);

        //(SNIP)

        serviceMock.Verify(x => x.GetSinglePatientViewTable(dateWindow, currentPatient, false), Times.Once);
    }
}

如果这种方法对您有效或无效,则取决于您的单元测试实际测试的内容。因为您检查Assert.IsTrue(vm.Jobs.Count > 0),看起来您在await GetData(...);调用后有额外的逻辑,所以这可能不适用于您当前的问题。但是,这可能对您需要为视图模型编写的其他单元测试有所帮助。

答案 1 :(得分:1)

这实际上取决于你要测试的内容:

  1. 测试RelayCommand是否已正确连接并调用async 方法
    1. 测试Async方法逻辑是否正确?
    2. 1。测试RelayCommand触发器

      1.a使用外部依赖关系验证

      根据我的个人经验,最简单的方法是测试触发器是否正确连接以执行命令,然后测试您的类是否按预期在某处与另一个外部类进行了交互。 E.g。

      private async void ExecuteLoadJobCommandAsync()
      {
         await GetData(...);
      }
      
      private async void GetData(...)
      {
         var data = await _repo.GetData();
         Jobs.Add(data);
      }
      

      很容易测试你的回购被调用。

          public void TestUsingExternalDependency()
          {
              _repo.Setup(r => r.GetData())
                  .Returns(Task.Run(() => 5))
                  .Verifiable();
      
              _vm.LoadJobCommand.Execute(null);
      
              _repo.VerifyAll();
          }
      

      我有时甚至会这样做,所以它不会尝试处理所有事情:

          [Test]
          public void TestUsingExternalDependency()
          {
              _repo.Setup(r => r.GetData())
                  .Returns(() => { throw new Exception("TEST"); })
                  .Verifiable();
      
              try
              {
                  _vm.LoadJobCommand.Execute(null);
              }
              catch (Exception e)
              {
                  e.Message.Should().Be("TEST");
              }
      
              _repo.VerifyAll();
          }
      

      1.b使用计划程序

      另一种选择是使用调度程序,并使用它来安排任务。

      public interface IScheduler
      {
          void Execute(Action action);
      }
      
      // Injected when not under test
      public class ThreadPoolScheduler : IScheduler
      {
          public void Execute(Action action)
          {
              Task.Run(action);
          }
      }
      
      // Used for testing
      public class ImmediateScheduler : IScheduler
      {
          public void Execute(Action action)
          {
              action();
          }
      }
      

      然后在你的ViewModel

          public ViewModelUnderTest(IRepository repo, IScheduler scheduler)
          {
              _repo = repo;
              _scheduler = scheduler;
              LoadJobCommand = new RelayCommand(ExecuteLoadJobCommandAsync);
          }
          private void ExecuteLoadJobCommandAsync()
          {
              _scheduler.Execute(GetData);
      
          }
      
          private void GetData()
          {
              var a =  _repo.GetData().Result;
              Jobs.Add(a);
          }
      

      你的考试

          [Test]
          public void TestUsingScheduler()
          {
              _repo.Setup(r => r.GetData()).Returns(Task.Run(() => 2));
      
              _vm = new ViewModelUnderTest(_repo.Object, new ImmediateScheduler());
      
              _vm.LoadJobCommand.Execute(null);
      
              _vm.Jobs.Should().NotBeEmpty();
          }
      

      2。测试GetData逻辑

      如果您要测试get GetData()逻辑或甚至ExecuteLoadJobCommandAsync()逻辑。那么你一定要把你想要测试的方法作为内部,并将你的方法标记为InternalsVisibleTo,这样你就可以直接从测试类中调用这些方法。

答案 2 :(得分:0)

为什么不用测试覆盖GetData(...)方法?我认为测试继电器命令没有任何意义