c# - 最小起订量在第二次验证时未执行任何调用

标签 c# asynchronous xamarin.forms nunit moq

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

当我运行测试时,第二次最小起订量验证检查失败。奇怪的是,我在类似的类(class)中做了完全相同的事情,而且效果很好。我一直在寻找类似的问题,但似乎只能找到返回异步任务的答案。

如果我调换在实际类中测试的方法调用的顺序,以便首先调用 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):

 public class ExerciseRatingViewModel
{
    public int DifficultyRating { get; set; }
    public int PainRating { get; set; }
    public int MobilityRating { get; set; }
    public ICommand SubmitRatingsCommand { get; }

    private readonly IPageService _pageService;
    private readonly IDataService _dataService;
    private readonly IAPIService _apiService;

    private int _sequenceID;

    public ExerciseRatingViewModel(int sequenceID, IPageService pageService, IDataService dataService, IAPIService apiService)
    {
        _sequenceID = sequenceID;
        _pageService = pageService;
        _dataService = dataService;
        _apiService = apiService;
        SubmitRatingsCommand = new Command(SubmitStats);
    }

    private async void 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());
    }      
}

这是我的测试类

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>()));
    }
}

PostStats 方法的签名是:

 Task<bool> PostStats(SequenceRating sequenceRating);

PushAsync 方法的签名是:

 Task PushAsync(Page page);

最佳答案

首先重构 View 模型以避免 async 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());
}      

引用 Async/Await - Best Practices in Asynchronous Programming

您正在使用 Task,因此测试需要是异步的,而且 mock 需要返回一个 Task 以允许异步按预期进行。您已经为 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>()));
}

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

关于c# - 最小起订量在第二次验证时未执行任何调用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54866772/

相关文章:

android - 将 Xamarin Forms View 呈现为 Android View 而不指定大小

sqlite - SQLite.SQLiteException:没有这样的功能:在获取CurrentDate时使用LINQ查询来字符串化

c# - 在 C# 中将错误消息与错误代码相关联的最佳方法是什么?

javascript - 无法等待 Node.js 中的 sqlite3.Database.get() 函数完成

java - 使用 SWF TryCatch 中 doCatch() 方法中 doTry() 方法中定义的变量

node.js - Nodejs 异步在完成之前调用排出回调

Xamarin.Forms 发布错误 bundles/armeabi-v7a/temp.c :1:39: fatal error: mono/metadata/mono-config. h: No such file or directory

c# - 获取内网认证用户

c# - 从 VB 到 C# 的正则表达式(LIKE 到正则表达式)

c# - C#注销后如何清除存储的数据?