Moq не вызовы выполняются на 2-й верификации - PullRequest
0 голосов
/ 25 февраля 2019

Я использую NUnit и Moq для тестирования некоторых простых классов.

Когда я запускаю тест, он не проходит во второй проверке moq verify.Странно то, что я сделал то же самое в аналогичном классе, и он отлично работает.Я искал похожие проблемы, но, похоже, нашел ответы только для возврата асинхронной задачи.

Если я поменяю местами порядок вызовов методов, которые я тестирую в реальном классе, чтобы сначала вызывался метод 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

Вот мой класс:

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

1 Ответ

0 голосов
/ 25 февраля 2019

Первая модель представления рефакторинга, чтобы исключить 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());
}      

Ссылка Асинхронное / ожидание - лучшие практики асинхронного программирования

Вы работаете с Task, поэтому тест должен быть асинхронным, а макет должен возвращать 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 используется в обратном вызове макета, чтобы можно было дождаться завершения асинхронного кода до того, какпытаясь проверить поведение.Это сделано из-за событий и команды, которые выполняются в отдельных потоках.

...