Donc, comme le titre l'indique, cela a fonctionné pour nous dans .NET Core 2.2 et Moq version 4.10.1. Après la mise à niveau vers .NET Core 3.1 et Moq version 4.14.5, la méthode de vérification échoue, indiquant que la méthode spécifiée n'a pas été appelée (aucune modification du code sous-jacent). J'ai reculé Moq à la version 4.10.1 juste pour voir si cela était dû à un changement dans la nouvelle version de Moq. Je reçois toujours la même erreur.

Tentative de vérification qu'un message de journal a été écrit sur ILogger.

Ce qui est étrange, c'est que si je débogue le test unitaire et que je regarde l'objet simulé avec une montre variable, cela montre que la méthode a bien été invoquée.

enter image description here

Code pertinent:

public class AuditFilter, IResultFilter
{
    ...     
    public void OnResultExecuted( ResultExecutedContext context )
    {
        if( !IsContextValid( context ) )
        { return; }
        ...
    }
    
    public override bool IsContextValid( FilterContext context )
    {
        if( context == null )
        { 
            Logger.Error( "Error writing to the audit log", new ArgumentNullException( nameof( context ) ) ); 
            return false;
        }

        if( context.HttpContext == null )
        { 
            Logger.LogError( "Error writing to the audit log", new ArgumentNullException( nameof( context.HttpContext ) ) );
            return false;
        }

        return true;
    }       

    public ILogger Logger { get; set; }
    ...
}

public class AuditTests : BaseTests
{
    ...     
    private Mock<ILogger> _mockLog;
    private AuditFilter _filter;

    [SetUp]
    public void Setup()
    {
        _mockLog = new Mock<Microsoft.Extensions.Logging.ILogger>();
        _mockLog.SetupAllProperties;
        _filter = new AuditFilter();
    }

    [Test]
    public void Service_Filters_Audit_IsContextValid_Context_Null()
    {
        var expected = false;
    
        _filter.Logger = _mockLog.Object;
        var actual = _filter.IsContextValid( null );
    
        _mockLog.Verify( logger => logger.Log( LogLevel.Error, 
            It.IsAny<EventId>(), 
            It.IsAny<object>(),
            It.IsAny<ArgumentNullException>(), 
            It.IsAny<Func<object, Exception, string>>() ),
            Times.Once );
    
        Assert.AreEqual( expected, actual );
    }       
    ...
}

Remarque: La méthode ILogger.LogError est une méthode d'extension Microsoft pour ILogger qui appelle la méthode ILogger.Log.

Voici l'exception qui est lancée. entrez la description de l'image ici

Remarque: Un entier est implicitement converti en Microsoft.Extensions.Logging.EventId, le deuxième type de paramètre d'entrée de la méthode ILogger.Log.

Ces signatures semblent me correspondre; donc je ne sais pas pourquoi il dit qu'il n'a pas été invoqué.

Pour réitérer, ce code fonctionnait avant la mise à niveau vers .NET Core 3.1 et fonctionne toujours dans notre code pré-forké.

Aussi, avant que quelqu'un ne suggère que la méthode doit être configurée: cela fonctionnait avant la mise à niveau sans elle, et je l'ai déjà essayée et cela n'a pas fonctionné.

2
EL MOJO 21 août 2020 à 21:56

2 réponses

Meilleure réponse

Comme je l'avais ajouté dans les commentaires, il s'agit d'un problème connu avec Moq et .NET Core directement à partir de la version Preview de .NET Core 3.0. Veuillez jeter un œil au problème github pour comprendre pourquoi cela a rompu avec la mise à jour.

En un mot, d'après le problème lié, l'échec ici est dû au fait que le type passé à la méthode Logger.Log est maintenant changé en FormattedLogValues. Bien qu'il existe un matcher de type générique It.IsAnyType introduit dans Moq 4.13.0, cela ne résoudrait pas le problème.

La raison en est que l'un des auteurs de Moq l'indique ici

Le point important (pour l'instant) est que It.IsAnyType n'est pas utilisé dans une position imbriquée dans l'argument de type It.IsAny<>.

Pour contourner le problème, nous devons remplacer It.IsAny<Func<object, Exception, string>>() avec (Func<object, Exception, string>)It.IsAny<object>()

La justification fournie pour cela est d'améliorer les performances du type matcher car le premier entraînerait la décomposition de tous les paramètres de type sous le capot pour voir s'il y a un type matcher inclus en premier lieu.

0
Sai Gummaluri 26 août 2020 à 05:23

Je pense que le problème sera cette partie de l'expression de vérification

It.IsAny<Func<object, Exception, string>>()

Sous les couvertures, ce n'est pas un Func<object, Exception, string> et le simulacre de correspondance IsAny ne correspond pas à cause de cette différence. Si vous le modifiez en

(Func<object, Exception, string>) It.IsAny<object>()

Il doit correspondre. Voici ce que j'ai choisi comme point de départ pour vérifier les expressions car cela fonctionne pour au moins .NET Core 2. * et les suivants:

loggerMock.Verify(x => x.Log(
      It.IsAny<LogLevel>(),
      It.IsAny<EventId>(),
      It.IsAny<IReadOnlyList<KeyValuePair<string, object>>>(),
      It.IsAny<Exception>(),
      (Func<object, Exception, string>) It.IsAny<object>()),
   Times.Exactly(expectedNumberOfInvocations));

Vous pouvez utiliser It.IsAny<object>() pour le troisième paramètre mais je trouve que c'est plus pratique lors de l'interrogation des propriétés.

J'ai récemment effectué pas mal de vérification des appels de journaux au travail pour les processus de boîte noire, alors maintenant j'utilise principalement Moq.Contrib.ExpressionBuilders.Logging pour créer des expressions de vérification fluides et lisibles. Avertissement: je suis l'auteur.

0
rgvlee 22 août 2020 à 01:13