我有一个方法,它有一行代码:

_logger.LogError(exception, $"Encountered {exception.GetType().Name}. Unable to verify user with id {user.UserId}");

这有一个对应的单元测试,其断言如下:

var logger = Substitute.For<ILogger<SyncService>>();

// other Arrange, Act, Assert steps

logger.Received(1).LogError(exception, "Encountered NullReferenceException. Unable to verify user with id 1");

这项测试运行良好.

但是,由于我们遇到了一些问题,现在需要将此日志(log)转换为 struct 化日志(log).

因此,现在类中的行如下所示:

_logger.LogError(exception, "Encountered {exceptionType}. Unable to verify user with id {userId}", exception.GetType().Name, user.UserId);

但现在,当我将断言更改为以下内容时,测试失败:

logger.Received(1).LogError(exception, "Encountered {exceptionType}. Unable to verify user with id {userId}", "NullReferenceException", 1);

错误消息如下.我删除了不需要的堆栈跟踪,以便只突出显示重要的部分:

NSubstitute.Exceptions.ReceivedCallsException : Expected to receive exactly 1 call matching:
    Log<FormattedLogValues>(Error, 0, Encountered NullReferenceException. Unable to verify user with id 1, System.NullReferenceException: Test Exception
   at NSubstitute.ExceptionExtensions.ExceptionExtensions.<>c__DisplayClass0_0.<Throws>b__0(CallInfo _)

   ...// complete stack trace ...

Actually received no matching calls.

Received 1 non-matching call (non-matching arguments indicated with '*' characters):
    Log<FormattedLogValues>(Error, 0, *Encountered NullReferenceException. Unable to verify user with id 1*, System.NullReferenceException: Test Exception
   at NSubstitute.ExceptionExtensions.ExceptionExtensions.<>c__DisplayClass0_0.<Throws>b__0(CallInfo _)

起初,我无法弄清楚自己做错了什么.从这两条消息看,似乎使用正确的参数调用了正确的方法,但该消息仍被标记为不匹配.

但在深入研究之后,我意识到标记的消息实际上是对FormattedLogValuesToString()调用,该调用在抛出异常时发生.在内部,它试图将string的实例与FormattedLogValues的实例进行比较

我try 在Log<FormattedLogValues>上直接断言,但似乎FormattedLogValues类不可用于外部使用.

这个问题早先是这样解决的:https://github.com/nsubstitute/NSubstitute/issues/384

但现在, struct FormattedLogValues不再可供公众使用.这里有一个悬而未决的问题:https://github.com/dotnet/runtime/issues/67577

但现在的问题是,我如何测试这一点?我知道Moq有一个名为It.IsAnyType()的方法,可以用来忽略消息模板的类型,但NSubicide有类似的方法吗?

我在StackOverflow中看到了其他一些有类似问题的帖子,但当使用 struct 化日志(log)时,答案似乎不起作用

推荐答案

有一件事你可以做,这是我自己在几个场合中用过的.

首先,创建我们将称为"可测试的"记录器抽象:

public abstract class TestableLogger<T> : ILogger<T>
{
    public abstract IDisposable? BeginScope<TState>(TState state)
        where TState : notnull;

    public abstract bool IsEnabled(LogLevel logLevel);

    public void Log<TState>(
        LogLevel logLevel,
        EventId eventId,
        TState state,
        Exception? exception,
        Func<TState, Exception?, string> formatter)
    {
        this.Log(logLevel, eventId, state?.ToString(), exception);
    }

    public abstract void Log(
        LogLevel logLevel,
        EventId eventId,
        string? state,
        Exception? exception);
}

这个包装器的目的是尽可能忠实地实现ILogger<T>,同时允许您对原始Log<TState>方法的细微变化做出断言:我们通过绕过内部Formatter/TState类来"简化"实现,只需在TState参数上调用ToString.

我们并不真正关心BeginScopeIsEnabled个原始成员,所以我们只是通过抽象地实现它们来"忽略它们".

然后,您现在可以替换这个新的抽象类,而不是原来的ILogger<T>,并对其进行断言,如下所示:

[TestClass]
public class UnitTest1
{
    [TestMethod]
    public void TestMethod1()
    {
        // Arrange
        var user = new User { UserId = 1 };
        var exception = new NullReferenceException();
        var logger = Substitute.For<TestableLogger<Service>>();
        var verifier = Substitute.For<IUserVerifier>();
        verifier
            .When(v => v.Verify(user))
            .Throw(exception);

        var service = new Service(logger, verifier);

        // Act
        service.DoStuff(user);

        // Assert
        logger.Received(1).Log(
            LogLevel.Error,
            Arg.Any<EventId>(),
            "Encountered NullReferenceException. Unable to verify user with id 1",
            exception);
    }
}

我已经对您的场景的其余部分做了一些假设,但这似乎是合理的.请注意,我在那里使用的是您的原始消息.

以下是我为支持此示例而创建的其余代码:

public interface IUserVerifier
{
    void Verify(User user);
}

public class User
{
    public int UserId { get; set; }
}

public class Service
{
    private readonly ILogger<Service> logger;
    private readonly IUserVerifier userVerifier;

    public Service(ILogger<Service> logger, IUserVerifier userVerifier)
    {
        this.logger = logger;
        this.userVerifier = userVerifier;
    }

    public void DoStuff(User user)
    {
        try
        {
            this.userVerifier.Verify(user);
        }
        catch (Exception ex)
        {
            this.logger.LogError(
                ex, 
                "Encountered {exceptionType}. Unable to verify user with id {userId}",
                ex.GetType().Name,
                user.UserId);
        }
    }
}

这里唯一的警告是,您不能单独断言作为 struct 化日志(log)记录参数传递的各个值,但它仍然是一个在某种程度上有效的断言.

这种方法的一个有趣方面是,您实际上可以根据自己的喜好定制新的抽象方法.我只删除了容易出问题的部分,但您可以完全更改该方法的形状,并仍然能够在其上断言,只要您执行与原始Log方法相同的调用重定向.

我目前还不知道有什么不同的方法可以让您专门测试每个参数.

Csharp相关问答推荐

为什么我的弹丸朝错误的方向移动?

C#序列化隐藏包含子类的列表的数组名称

ASP.NET Core将SON重新序列化为派生类

在ASP.NET中为数据注释 Select 合适的语言

EF Core Fluent API中定义的多对多关系

获取ASP.NET核心身份认证cookie名称

WPF Windows初始化正在锁定. Net 8中分离的线程

使用命令初始化可绑定属性

ASP.NET Core 8.0 JWT验证问题:尽管令牌有效,但SecurityTokenNoExpirationError异常

碰撞检测与盒碰撞器,其isTrigger on问题

应用程序重新启动后,EFCore列表的BSON反序列化错误

在具有不同属性名称的两个类之间创建关系

LINQ to Entities中的加权平均值

在使用StringBuilder时,如何根据 colored颜色 设置为richTextBox中的特定行着色?

无法使用[FromForm]发送带有图像和JSON的多部分请求

Blazor Server.NET 8中的Blazore.FluentValidation问题

WinUI 3中DoubleCollection崩溃应用程序类型的依赖属性

从Base64转换为不同的字符串返回相同的结果

如何使用EPPlus C#在单个单元格中可视化显示多行文字

ASP.NET核心中的授权,如何在DbContext启动之前提供租用ID