如何注入不提供接口和虚拟方法的类的假实现?

Posted

技术标签:

【中文标题】如何注入不提供接口和虚拟方法的类的假实现?【英文标题】:How to inject a fake implementation of a class providing no interface and no virtual methods? 【发布时间】:2022-01-05 02:31:24 【问题描述】:

我正在使用 Discord.Net 包为我提供 DiscordSocketClient 类,该类目前是我的 DI 容器中的单例。我需要实例中的几种方法

Task LoginAsync(TokenType tokenType, string token, bool validateToken = true) Task StartAsync() Task LogoutAsync() Task StopAsync()

给定这个使用方法的示例类

public sealed class ConsumingClass

    private readonly DiscordSocketClient _discordSocketClient;
    
    public ConsumingClass(DiscordSocketClient discordSocketClient)
    
        _discordSocketClient = discordSocketClient;
    
    
    public override async Task Initialize(CancellationToken cancellationToken)
    
        await _discordSocketClient.LoginAsync(TokenType.Bot, "my-token");
        await _discordSocketClient.StartAsync();
    
    
    public override async Task TearDown(CancellationToken cancellationToken)
    
        await _discordSocketClient.LogoutAsync();
        await _discordSocketClient.StopAsync();
    

我想使用 XUnit 和 Moq 创建测试,以确保 ConsumingClass 的实例按预期工作。

很遗憾,DiscordSocketClient 不提供接口,因此我无法在我的测试中注入模拟实现。

这些方法也不是虚拟的,因此无法创建Mock<DiscordSocketClient> 的实例并使用.Setup() 来设置模拟实现。

那么我如何在我的测试用例中验证客户端方法是否已被调用一次?例如。 _discordSocketClientMock.Verify(discordSocketClient => discordSocketClient.LoginAsync(), Times.Once());

我是否必须创建一个接口IMyDiscordSocketClient 提供这些方法和一个类,另一个类MyDiscordSocketClient 继承自DiscordSocketClient 并实现该接口并使用它?或者有没有更好的方法?

【问题讨论】:

【参考方案1】:

如果DiscordSocketClient 没有(公共)虚拟成员,您也不能真正从中派生。但是,您的最后一个建议可能仍然是您拥有的最好的可能性,除了 MyDiscordSocketClient 不应该从 DiscordSocketClient 派生,而是聚合一个。


public class MyDiscordSocketClient: IMyDiscordSocketClient

    private DiscordSocketClient _client;
    public MyDiscordSocketClient(DiscordSocketClient client)
    
        _client = _client;
    
    
    public async Task Initialize(CancellationToken cancellationToken)
    
         _client.Initialize(cancellationToken);
    

    // etc...

【讨论】:

感谢您的回答。但是这样做,我仍然无法检查 LoginAsyncStartAsync 是否已被调用,对吗?所以自定义包装器不会提供任何好处 @Question3r。你当然可以。由于您的ConsumingClass 将在构造函数中使用IMyDiscordSocketClient 而不是DiscordSocketClient,因此您可以为它提供一个模拟。您不能对MyDiscordSocketClient 进行单元测试,但这没有任何意义,因为它不包含任何逻辑。

以上是关于如何注入不提供接口和虚拟方法的类的假实现?的主要内容,如果未能解决你的问题,请参考以下文章

g++不能对具有虚拟方法的类的实例的运行时大小进行更多优化吗?

深入理解java虚拟机,类加载

在 C++ 中虚拟基类的两个实现之间共享方法

什么是依赖注入的 Pythonic 方式?

C#中接口和抽象类的区别

运用Unity实现依赖注入[结合简单三层实例]