我正在使用 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/