Skip to content

Moq.VerifyLogUnexpectedException #15

@kippermand

Description

@kippermand

When running unit tests to verify logger is called as expected, Moq.ILogger threw this exception:

Moq.VerifyLogUnexpectedException : Moq.ILogger found an unexpected exception.
----> System.ArgumentNullException : Value cannot be null. (Parameter 'body')

Stack Trace:

    VerifyLogExtensions.Verify[T](Mock`1 loggerMock, Expression`1 expression, Nullable`1 times, Func`1 timesFunc, String failMessage)
    VerifyLogExtensions.VerifyLog(Mock`1 loggerMock, Expression`1 expression, Func`1 times)
    <>c__DisplayClass22_0.<ExampleAsync_HallOfFameInducteesNotEmpty_NoDuplicateIds_ValidatorIsValidTrue_RetrieveBOExecuteSuccessful_NewEntryAlreadyInSameCategory_ErrorSet>b__1() line 250
    Assert.Multiple(TestDelegate testDelegate)
    BusinessLogic.ExampleAsync_HallOfFameInducteesNotEmpty_NoDuplicateIds_ValidatorIsValidTrue_RetrieveBOExecuteSuccessful_NewEntryAlreadyInSameCategory_ErrorSet() line 248
    GenericAdapter`1.GetResult()
    AsyncToSyncAdapter.Await(Func`1 invoke)
    TestMethodCommand.RunTestMethod(TestExecutionContext context)
    TestMethodCommand.Execute(TestExecutionContext context)
    <>c__DisplayClass1_0.<Execute>b__0()
    DelegatingTestCommand.RunTestMethodInThreadAbortSafeZone(TestExecutionContext context, Action action)
    --ArgumentNullException
    ContractUtils.RequiresNotNull(Object value, String paramName, Int32 index)
    ExpressionUtils.RequiresCanRead(Expression expression, String paramName, Int32 idx)
    Expression.ValidateLambdaArgs(Type delegateType, Expression& body, ReadOnlyCollection`1 parameters, String paramName)
    Expression.Lambda[TDelegate](Expression body, String name, Boolean tailCall, IEnumerable`1 parameters)
    Expression.Lambda[TDelegate](Expression body, Boolean tailCall, IEnumerable`1 parameters)
    VerifyLogExpressionArgs.From(Expression expression)
    VerifyLogExpression.From(Expression expression)
    VerifyLogExtensions.Verify[T](Mock`1 loggerMock, Expression`1 expression, Nullable`1 times, Func`1 timesFunc, String failMessage)

Code:

public async Task ExampleAsync(IEnumerable<Models.HallOfFame> hallOfFameInductees, CancellationToken cancellationToken = default)
{
    var currentHallOfFamers = (await RetrieveBO.ExecuteAsync(cancellationToken)).ToList();

    var alreadyInducted = (from current in currentHallOfFamers
                            from inductee in hallOfFameInductees
                            where current.BowlerId == inductee.BowlerId
                            where AlreadyInducted(inductee, current)
                            select inductee).ToList();

    if (alreadyInducted.Count > 0)
    {
        Logger.LogError("Bowler(s) {alreadyInducted} already inducted to hall of fame", alreadyInducted);
        ErrorDetails.Add(new Models.ErrorDetail("Invalid Addition: Bowler already inducted"));

        return;
    }

    DataLayer.Execute(hallOfFameInductees);

    await DataAccess.CommitAsync(LocalTransaction, cancellationToken);
}

private static bool AlreadyInducted(Models.HallOfFame inductee, Models.HallOfFame current)
    => current.Category == Models.HallOfFameTypes.Combined || current.Category == inductee.Category;

Unit Test (hallOfFameInductees and hallOfFamers generated via Bogus, Fluent calls override properties set via Bogus):

[Test]
public async Task ExampleAsync_RetrieveBOExecuteSuccessful_NewEntryAlreadyInSameCategory_LoggerLogError_CalledCorrectly()
{
    var hallOfFameInductees = new[] { new Builders.Models.HallOfFame()
                                            .WithBowlerId(1)
                                            .WithYear(2000)
                                            .WithCategory(NEBA.Models.HallOfFameTypes.Performance)
                                            .Generate()};

    var hallOfFamers = new[] { new Builders.Models.HallOfFame()
                                            .WithBowlerId(1)
                                            .WithYear(1999)
                                            .WithCategory(NEBA.Models.HallOfFameTypes.Performance)
                                            .Generate()};

    _retrieveBO.Setup(retrieveBO => retrieveBO.ExecuteAsync(It.IsAny<CancellationToken>())).ReturnsAsync(hallOfFamers);

    await _businessLogic.ExampleAsync(hallOfFameInductees);

    _logger.VerifyLog(logger => logger.LogError("Bowler(s) {alreadyInducted} already inducted to hall of fame", hallOfFameInductees), Times.Once);
}

Models:

/// <summary>
/// 
/// </summary>
public sealed class HallOfFame
{
    /// <summary>
    /// 
    /// </summary>
    /// <value></value>
    public int Id { get; set; }

    /// <summary>
    /// 
    /// </summary>
    /// <value></value>
    public int BowlerId { get; set; }

    /// <summary>
    /// 
    /// </summary>
    /// <value></value>
    public int Year { get; set; }

    /// <summary>
    /// 
    /// </summary>
    /// <value></value>
    public HallOfFameTypes Category { get; set; }
}

/// <summary>
/// 
/// </summary>
public enum HallOfFameTypes
{
    /// <summary>
    /// 
    /// </summary>
    Performance = 100,

    /// <summary>
    /// 
    /// </summary>
    Service = 200,

    /// <summary>
    /// 
    /// </summary>
    Combined = 300,

    /// <summary>
    /// 
    /// </summary>
    Friend = 500
}

Metadata

Metadata

Assignees

Labels

No labels
No labels

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions