2017-04-06 5 views
5

Nous avons récemment commencé à utiliser MediatR pour nous permettre de désencombrer les actions des contrôleurs lorsque nous remodelons un grand portail orienté client et que nous le convertissons en C#. Dans le cadre de cela, nous augmentons également notre couverture de tests unitaires, mais j'ai rencontré un problème en essayant de se moquer de MediatR lui-même.Mocking MediatR 3 avec Moq

La commande fait un tas de choses pour lancer un processus et une partie de ceci envoie une notification. La notification elle-même est traitée par son propre gestionnaire et serait donc soumise à son propre test unitaire, donc je veux me moquer de MediatR pour que l'appel this.mediator.Send(message) ne fasse vraiment rien. Le gestionnaire renvoie un objet mais nous ne nous en soucions pas dans ce contexte, donc à toutes fins utiles, nous le traitons comme un retour void. Je veux juste vérifier que Send a été appelée une fois dans le cadre du test. Cependant, la méthode Send lance un NullReferenceException et je ne sais pas pourquoi. Depuis la version 3, MediatR prend maintenant un deuxième paramètre optionnel sur Send, un CancellationToken, et les arbres d'expression nécessitent que vous les définissiez explicitement, vous devez donc spécifier une valeur. Je n'ai jamais rencontré cela auparavant et dans mon esprit, je pense que cela pourrait faire partie du problème, mais cela peut être une confusion de ma part.

Voici une illustration réduite.

SUT

public class TransferHandler : IAsyncRequestHandler<TransferCommand, TransferResult> 
{ 
    private readonly IMediator mediator; 

    public TransferHandler(IMediator mediator) 
    { 
     this.mediator = mediator; 
    } 

    public async Task<TransferResult> Handle(TransferCommand message) 
    { 
     // Other stuff. 
     var notification = new TransferNotificationCommand() 
     { 
      ClientId = message.clientId, 
      OfficeId = message.OfficeId, 
      AuthorityFileId = letter?.Id 
     }; 

     await this.mediator.Send(notification); // <=== This is where we get a NullReferenceException, even though nothing is actually null (that I can see). 

     return new TransferResult() 
     { 
      Transfer = transfer, 
      FileId = letter?.Id 
     } 
    } 
} 

test

public class TransferHandlerTests 
{ 
    [Theory] 
    [AutoData] 
    public async void HandlerCreatesTransfer(Mock<IMediator> mockMediator) 
    { 
     // Note that default(CancellationToken) is the default value of the optional argument. 
     mediator.Setup(m => m.Send(It.IsAny<TransferNotificationCommand>(), default(CancellationToken))).Verifiable("Notification was not sent."); 

     var handler = new TransferHandler(mediator.Object); 

     var actual = await handler.Handle(message); 

     mediator.Verify(x => x.Send(It.IsAny<CreateIsaTransferNotificationCommand>(), default(CancellationToken)), Times.Once()); 
    } 
} 

Qu'est-ce que je manque? J'ai l'impression d'avoir fait une erreur fondamentale quelque part mais je ne sais pas où.

Répondre

8

Vous devez gérer l'attente de l'opération asynchrone des méthodes Send à mesure qu'elles renvoient des tâches.

/// <summary> 
/// Asynchronously send a request to a single handler 
/// </summary> 
/// <typeparam name="TResponse">Response type</typeparam> 
/// <param name="request">Request object</param> 
/// <param name="cancellationToken">Optional cancellation token</param> 
/// <returns>A task that represents the send operation. The task result contains the handler response</returns> 
Task<TResponse> Send<TResponse>(IRequest<TResponse> request, CancellationToken cancellationToken = default(CancellationToken)); 

/// <summary> 
/// Asynchronously send a request to a single handler without expecting a response 
/// </summary> 
/// <param name="request">Request object</param> 
/// <param name="cancellationToken">Optional cancellation token</param> 
/// <returns>A task that represents the send operation.</returns> 
Task Send(IRequest request, CancellationToken cancellationToken = default(CancellationToken)); 

Cela signifie que vous devez avoir le retour simulé une tâche pour permettre au processus async de poursuivre le flux

mediator 
    .Setup(m => m.Send(It.IsAny<TransferNotificationCommand>(), It.IsAny<CancellationToken>())) 
    .ReturnsAsync(new Notification()) //<-- return Task to allow await to continue 
    .Verifiable("Notification was not sent."); 

//...other code removed for brevity 

mediator.Verify(x => x.Send(It.IsAny<CreateIsaTransferNotificationCommand>(), It.IsAny<CancellationToken>()), Times.Once()); 
+0

Comme je l'ai dit - il devait être quelque chose de fondamental et stupide !! Je supposais que puisque je ne me souciais pas du retour, je pouvais l'omettre, alors qu'en fait, je ne dis pas au moqueur quoi faire du tout, donc aucune tâche n'a été retournée. Ca a du sens maintenant, merci! J'ai édité votre réponse cependant car les retours étaient légèrement dehors. –

+1

@StevePettifer, je ne savais pas quelle était votre réponse basée sur le code fourni dans OP. Votre mise à jour est exacte. – Nkosi