如何对执行异步方法的 RelayCommand 进行单元测试?
How to unit test RelayCommand that Executes an async method?
由于没有 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)
为什么不用测试覆盖 GetData(...) 方法?我认为测试中继命令没有任何意义
我没有使用 async/await 但我 运行 过去遇到过类似的问题。我所处的情况是在其自身内部调用 Task.Run(
的方法,并且单元测试正在验证 ViewModel 是否使用正确的参数以正确的次数调用服务。
我们解决这个问题的方法是让被调用服务的 Mock 使用 ManualResetEventSlim
,然后单元测试在继续之前等待调用重置事件。
[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(...);
调用之后有额外的逻辑,所以这可能不适用于您当前的问题。但是,这可能对您需要为视图模型编写的其他单元测试有所帮助。
这实际上取决于您要测试的内容:
- 测试
RelayCommand
是否正确连接并呼叫您的 async
方法?
或
- 测试
Async
方法逻辑是否正确?
1。测试 RelayCommand 触发器
1.a 使用外部依赖来验证
根据我的个人经验,最简单的方法是测试触发器是否正确连接以执行命令,然后测试您的 class 是否按预期与另一个外部 class 交互。例如
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()
逻辑。那么你绝对应该将你想要测试的方法设置为 Internal,并将你的程序集标记为 InternalsVisibleTo
以便你可以直接从你的测试中调用这些方法 class.
由于没有 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)
为什么不用测试覆盖 GetData(...) 方法?我认为测试中继命令没有任何意义
我没有使用 async/await 但我 运行 过去遇到过类似的问题。我所处的情况是在其自身内部调用 Task.Run(
的方法,并且单元测试正在验证 ViewModel 是否使用正确的参数以正确的次数调用服务。
我们解决这个问题的方法是让被调用服务的 Mock 使用 ManualResetEventSlim
,然后单元测试在继续之前等待调用重置事件。
[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(...);
调用之后有额外的逻辑,所以这可能不适用于您当前的问题。但是,这可能对您需要为视图模型编写的其他单元测试有所帮助。
这实际上取决于您要测试的内容:
- 测试
RelayCommand
是否正确连接并呼叫您的async
方法?
或
- 测试
Async
方法逻辑是否正确?
1。测试 RelayCommand 触发器
1.a 使用外部依赖来验证
根据我的个人经验,最简单的方法是测试触发器是否正确连接以执行命令,然后测试您的 class 是否按预期与另一个外部 class 交互。例如
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()
逻辑。那么你绝对应该将你想要测试的方法设置为 Internal,并将你的程序集标记为 InternalsVisibleTo
以便你可以直接从你的测试中调用这些方法 class.