I am using NUnit and Moq to test some simple classes.
When I run the test it fails on the second moq verify check. What's strange is that I have done exactly the same thing in a similar class and it works fine. I have looked for similar issues but only seem to find answers for returning an async Task.
If I swap the order of the method calls I'm testing in the actual class so that the PushAsync method is called first then it's the PostStats verify that fails. This would make me think it can only do one verify but I've done two in my other test fine (both with the same return types).
The error I get is:
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
Here is my 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());
}
}
Here is my Test Class
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>()));
}
}
The signature for the PostStats method is:
Task<bool> PostStats(SequenceRating sequenceRating);
The signature for the PushAsync method is:
Task PushAsync(Page page);
First refactor view model to avoid async void functions, except for actual event handlers
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());
}
Reference Async/Await - Best Practices in Asynchronous Programming
You are working with Task so the test would need to be async and also the mock would need to return a Task to allow for the async to flow as expected. You already did it for PostStats. Now just do the same for 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>()));
}
In the test, a TaskCompletionSource is used in a callback of the mock in order to allow for the awaiting of the async code to be completed before trying to verify behavior. This is done because of the events and the command that are executing on separate threads.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With