C# 第二次验证时未执行Moq调用

C# 第二次验证时未执行Moq调用,c#,asynchronous,xamarin.forms,nunit,moq,C#,Asynchronous,Xamarin.forms,Nunit,Moq,我正在使用NUnit和Moq测试一些简单的类 当我运行测试时,第二次moq验证检查失败。奇怪的是,我在一个类似的班级里做了完全相同的事情,效果很好。我寻找过类似的问题,但似乎只找到了返回异步任务的答案 如果我交换在实际类中测试的方法调用的顺序,以便首先调用PushAsync方法,那么失败的是PostStats验证。 这会让我觉得它只能进行一次验证,但在我的另一个测试中,我已经完成了两次验证(都使用相同的返回类型) 我得到的错误是: Message: Moq.MockException : Ex

我正在使用NUnit和Moq测试一些简单的类

当我运行测试时,第二次moq验证检查失败。奇怪的是,我在一个类似的班级里做了完全相同的事情,效果很好。我寻找过类似的问题,但似乎只找到了返回异步任务的答案

如果我交换在实际类中测试的方法调用的顺序,以便首先调用PushAsync方法,那么失败的是PostStats验证。 这会让我觉得它只能进行一次验证,但在我的另一个测试中,我已经完成了两次验证(都使用相同的返回类型)

我得到的错误是:

Message: Moq.MockException : 
Expected invocation on the mock at least once, but was never performed: p => p.PushAsync(It.IsAny<ExerciseNotes>())

Configured setups: 
IPageService p => p.PushAsync(It.IsAny<ExerciseNotes>())
No invocations performed
这是我的测试课

class ExerciseRatingViewModelTests
{
    private ExerciseRatingViewModel _exerciseRatingViewModel;
    private Mock<IPageService> _pageService;
    private Mock<IDataService> _dataService;
    private Mock<IAPIService> _apiService;


    [SetUp]
    public void Setup()
    {
        _pageService = new Mock<IPageService>();
        _dataService = new Mock<IDataService>();
        _apiService = new Mock<IAPIService>();
        int sequenceID = 1;
        _exerciseRatingViewModel = new ExerciseRatingViewModel(sequenceID, _pageService.Object, _dataService.Object, _apiService.Object);
    }

    [Test()]
    public void SubmitStats_WhenTouched_ShouldNavigateToNotesPage()
    {

        //Arrange
        _apiService.Setup(a => a.PostStats(It.IsAny<SequenceRating>())).ReturnsAsync(true);
        _pageService.Setup(p => p.PushAsync(It.IsAny<ExerciseNotes>()));


        // Act
        _exerciseRatingViewModel.SubmitRatingsCommand.Execute(null);

        //Assert
        _apiService.Verify(a => a.PostStats(It.IsAny<SequenceRating>()));
        _pageService.Verify(p => p.PushAsync(It.IsAny<ExerciseNotes>()));
    }
}

首先重构视图模型以避免
异步void
函数,实际事件处理程序除外

public ExerciseRatingViewModel(int sequenceID, IPageService pageService, IDataService dataService, IAPIService apiService) {
    _sequenceID = sequenceID;
    _pageService = pageService;
    _dataService = dataService;
    _apiService = apiService;
    submitted += onSubmitted; //subscribe to event
    SubmitRatingsCommand = new Command(() => submitted(null, EventArgs.Empty));
}

private event EventHandler submitted = delegate { };
private async void onSubmitted(object sender, EventArgs args) { //event handler
    await SubmitStats();
}

private async Task SubmitStats() {
    int userID = _dataService.GetUserID();
    SequenceRating sequenceRating = new SequenceRating(_sequenceID, userID, DifficultyRating, PainRating, MobilityRating);
    bool success = await _apiService.PostStats(sequenceRating);
    await _pageService.PushAsync(new ExerciseNotes());
}      
参考文献


您使用的是
任务
,因此测试需要是异步的,模拟也需要返回
任务
,以允许异步按预期进行。您已经为
PostStats
完成了此操作。现在只需对
PushAsync
执行相同的操作

[Test()]
public async Task SubmitStats_WhenTouched_ShouldNavigateToNotesPage() {
    //Arrange
    var tcs = new TaskCompletionSource<object>();
    _apiService.Setup(a => a.PostStats(It.IsAny<SequenceRating>())).ReturnsAsync(true);
    _pageService.Setup(p => p.PushAsync(It.IsAny<Page>()))
        .Callback((Page arg) => tcs.SetResult(null))
        .Returns(Task.FromResult((object)null));

    // Act
    _exerciseRatingViewModel.SubmitRatingsCommand.Execute(null);

    await tcs.Task; //wait for async flow to complete

    //Assert
    _apiService.Verify(a => a.PostStats(It.IsAny<SequenceRating>()));
    _pageService.Verify(p => p.PushAsync(It.IsAny<ExerciseNotes>()));
}
[Test()]
公共异步任务提交状态\u何时接触\u应导航到notespage(){
//安排
var tcs=new TaskCompletionSource();
_apiService.Setup(a=>a.PostStats(It.IsAny()).ReturnsAsync(true);
_pageService.Setup(p=>p.PushAsync(It.IsAny()))
.Callback((页面参数)=>tcs.SetResult(null))
.Returns(Task.FromResult((object)null));
//表演
_exerciseRatingViewModel.SubmittingsCommand.Execute(空);
wait tcs.Task;//等待异步流完成
//断言
_验证(a=>a.PostStats(It.IsAny());
_验证(p=>p.PushAsync(It.IsAny());
}

在测试中,在模拟的回调中使用了
TaskCompletionSource
,以便在尝试验证行为之前等待异步代码完成。这是因为事件和命令在不同的线程上执行

您使用的是
任务
,因此测试需要是异步的,模拟也需要返回
任务
,以允许异步按预期进行。您已经为
PostStats
完成了此操作。现在只需对
PushAsync
执行相同的操作,避免
async void
事件处理程序除外。这会让事情变得更棘手谢谢您的回答,我已经按照您的建议重构了代码。但它似乎不太正常,因为测试永远不会结束。@HenryJ好的,让我重新检查代码。我还建议在事件处理程序中放置一些断点,以确保它们被调用。我应该提到,应用程序与您提供的代码配合良好,只是测试永远不会完成谢谢您对代码的更新,但测试仍然没有完成。我必须将回调类型从ExerciseNotes对象更改为页面,因为这是服务作为参数的基类。我试过几种不同的方法,但似乎都不明白working@HenryJ我刚刚花了一些时间重新创建了到目前为止您所展示的所有代码,并在一个单元测试中运行了它,当使用我提供的答案中的细节(包括对页面的更改)进行练习时,该测试按预期通过
 Task PushAsync(Page page);
public ExerciseRatingViewModel(int sequenceID, IPageService pageService, IDataService dataService, IAPIService apiService) {
    _sequenceID = sequenceID;
    _pageService = pageService;
    _dataService = dataService;
    _apiService = apiService;
    submitted += onSubmitted; //subscribe to event
    SubmitRatingsCommand = new Command(() => submitted(null, EventArgs.Empty));
}

private event EventHandler submitted = delegate { };
private async void onSubmitted(object sender, EventArgs args) { //event handler
    await SubmitStats();
}

private async Task SubmitStats() {
    int userID = _dataService.GetUserID();
    SequenceRating sequenceRating = new SequenceRating(_sequenceID, userID, DifficultyRating, PainRating, MobilityRating);
    bool success = await _apiService.PostStats(sequenceRating);
    await _pageService.PushAsync(new ExerciseNotes());
}      
[Test()]
public async Task SubmitStats_WhenTouched_ShouldNavigateToNotesPage() {
    //Arrange
    var tcs = new TaskCompletionSource<object>();
    _apiService.Setup(a => a.PostStats(It.IsAny<SequenceRating>())).ReturnsAsync(true);
    _pageService.Setup(p => p.PushAsync(It.IsAny<Page>()))
        .Callback((Page arg) => tcs.SetResult(null))
        .Returns(Task.FromResult((object)null));

    // Act
    _exerciseRatingViewModel.SubmitRatingsCommand.Execute(null);

    await tcs.Task; //wait for async flow to complete

    //Assert
    _apiService.Verify(a => a.PostStats(It.IsAny<SequenceRating>()));
    _pageService.Verify(p => p.PushAsync(It.IsAny<ExerciseNotes>()));
}