如何模拟包装的 Microsoft ASP.NET Core Client SignalR HubConnection class?
How to mock a wrapped Microsoft ASP.NET Core Client SignalR HubConnection class?
我正在实施我的第一个 signalR 应用程序,但遇到了测试方面的障碍。我正在使用 Moq
框架。
不幸的是,我发现微软的 HubConnection class is not easily mockable using Moq
framework, i.e. it does not have virtual methods and does not directly implement an interface. Microsoft's official suggestion 是供开发人员实现包装器 class 和接口的,我已经完成了,请参见 class [=19= 的小示例] 以下。我现在遇到的问题是如何测试wrapped HubConnection
class 是由wrapper触发的?
我最初从 HubConnection 派生了一个 class,下面的 MockedHubConnection
使用 new
来尝试覆盖。 MockedHubConnection
方法抛出 NotImplementedException
。在下面的测试中,它断言 NotImplementedException 被抛出以表示包装的 HubConnection 已被触发。但是,我从测试中收到 NullReference 异常:
Actual: typeof(System.NullReferenceException): Object reference not set to an instance of an object.
---- System.NullReferenceException : Object reference not set to an instance of an object.
Stack Trace:
at Microsoft.Extensions.Logging.Logger`1.Microsoft.Extensions.Logging.ILogger.BeginScope[TState](TState state)
at Microsoft.AspNetCore.SignalR.Client.HubConnection.StopAsync(CancellationToken cancellationToken)
----- Inner Stack Trace -----
at Microsoft.Extensions.Logging.Logger`1.Microsoft.Extensions.Logging.ILogger.BeginScope[TState](TState state)
at Microsoft.AspNetCore.SignalR.Client.HubConnection.StopAsync(CancellationToken cancellationToken)
我认为发生了什么,是 HubProxy
class 的构造函数隐含地将模拟 HubConnection 转换为真实的 HubConnection
实例??
如何测试包装第三方 class 的包装器 class,而第三方 class 不提供可覆盖的可模拟接口或虚拟方法?
Hub 代理示例 - 包装 HubConnection
public class HubProxy : IHubProxy
{
private readonly HubConnection connection;
public HubProxy(HubConnection connection)
{
this.connection = connection;
}
public Task StopAsync(CancellationToken cancel=default)
{
this.connection.StopAsync(cancel);
}
}
模拟 HubConnection - 抛出 NotImplementedException 以测试包装器调用
public class MockHubConnection : HubConnection
{
public MockHubConnection(
IConnectionFactory connection,
IHubProtocol protocol,
EndPoint endPoint,
IServiceProvider provider,
ILoggerFactory factory,
IRetryPolicy policy
) : base(connection, protocol, endPoint, provider, factory, policy) { }
public new Task StopAsync(CancellationToken token = default)
{
throw new NotImplementedException();
}
}
模拟 HubConnection - 初始尝试 - 使用 new 覆盖
public class MockHubConnection : HubConnection
{
public MockHubConnection(
IConnectionFactory connection,
IHubProtocol protocol,
EndPoint endPoint,
IServiceProvider provider,
ILoggerFactory factory,
IRetryPolicy policy
) : base(connection, protocol, endPoint, provider, factory, policy) { }
public new Task StopAsync(CancellationToken token = default)
{
throw new NotImplementedException();
}
}
测试样本
[Fact]
public async Task HubProxy_StopAsync_Invokes_HubConnection_StopAsync()
{
var mockCon = Mock.Of<IConnectionFactory>();
var mockHubProtocol = Mock.Of<IHubProtocol>();
var mockServiceProvider = Mock.Of<IServiceProvider>();
var mockLoggerFactory = Mock.Of<ILoggerFactory>();
var mockRetryPolicy = Mock.Of<IRetryPolicy>();
var mockHubConnection = new MockHubConnection(
mockCon,
mockHubProtocol,
new IPEndPoint(0, 0),
mockServiceProvider,
mockLoggerFactory,
mockRetryPolicy
);
var _client = new HubProxy(mockHubConnection);
await Assert.ThrowsAsync<NotImplementedException>(() => _client.StopAsync());
}
创建包装器 class 背后的整个想法是模拟包装器 class。包装器 class 仅充当您尝试使用的实际 class 的传递,因此它不会有任何自己的逻辑。由于您使用的是最小起订量,因此无需为 HubConnection
创建模拟 class。你会嘲笑 IHubProxy
所以你不使用与 HubConnection
.
相关的任何东西
var mockHubProxy = new Mock<IHubProxy>();
// Since you're only trying find out if a
// method is called then you would use MOQ's verify method.
mockHubProxy.Verify(x => x.StopAsync());
// If you really want an exception to be thrown then you
// would do the following. Though I don't think this is the way to go.
mockHubProxy.Setup(x => x.StopAsync()).Throws(new NotImplementedException());
这有意义吗?注意这里没有关于 HubConnection
的任何内容。包装器 HubProxy
的全部要点是删除与 HubConnection
相关的所有内容,因为它无法测试。在 HubProxy
的实际实现中,您的 StopAsync
方法将调用 HubConnection.StopAsync()
的具体实现。在你的模拟中它什么都不做。它只是确保它被调用。
我不知道 HubConnection
的方法,所以我将在下面的示例中使用虚构的方法名称来说明如何为实际具有 [=36= 的方法实现包装器] 值。
public interface IHubProxy
{
string GetStringValue();
}
public HubProxy: IHubProxy
{
private readonly HubConnection _connection;
public HubProxy()
{
_connection = new HubConnection();
}
public string GetStringValue()
{
return _connection.GetStringValue();
}
}
你的 mock 看起来像这样
var mockHubProxy = new Mock<IHubProxy>();
mockHubProxy.Setup(x => x.GetStringValue()).Returns("expected string value")
我正在实施我的第一个 signalR 应用程序,但遇到了测试方面的障碍。我正在使用 Moq
框架。
不幸的是,我发现微软的 HubConnection class is not easily mockable using Moq
framework, i.e. it does not have virtual methods and does not directly implement an interface. Microsoft's official suggestion 是供开发人员实现包装器 class 和接口的,我已经完成了,请参见 class [=19= 的小示例] 以下。我现在遇到的问题是如何测试wrapped HubConnection
class 是由wrapper触发的?
我最初从 HubConnection 派生了一个 class,下面的 MockedHubConnection
使用 new
来尝试覆盖。 MockedHubConnection
方法抛出 NotImplementedException
。在下面的测试中,它断言 NotImplementedException 被抛出以表示包装的 HubConnection 已被触发。但是,我从测试中收到 NullReference 异常:
Actual: typeof(System.NullReferenceException): Object reference not set to an instance of an object.
---- System.NullReferenceException : Object reference not set to an instance of an object.
Stack Trace:
at Microsoft.Extensions.Logging.Logger`1.Microsoft.Extensions.Logging.ILogger.BeginScope[TState](TState state)
at Microsoft.AspNetCore.SignalR.Client.HubConnection.StopAsync(CancellationToken cancellationToken)
----- Inner Stack Trace -----
at Microsoft.Extensions.Logging.Logger`1.Microsoft.Extensions.Logging.ILogger.BeginScope[TState](TState state)
at Microsoft.AspNetCore.SignalR.Client.HubConnection.StopAsync(CancellationToken cancellationToken)
我认为发生了什么,是 HubProxy
class 的构造函数隐含地将模拟 HubConnection 转换为真实的 HubConnection
实例??
如何测试包装第三方 class 的包装器 class,而第三方 class 不提供可覆盖的可模拟接口或虚拟方法?
Hub 代理示例 - 包装 HubConnection
public class HubProxy : IHubProxy
{
private readonly HubConnection connection;
public HubProxy(HubConnection connection)
{
this.connection = connection;
}
public Task StopAsync(CancellationToken cancel=default)
{
this.connection.StopAsync(cancel);
}
}
模拟 HubConnection - 抛出 NotImplementedException 以测试包装器调用
public class MockHubConnection : HubConnection
{
public MockHubConnection(
IConnectionFactory connection,
IHubProtocol protocol,
EndPoint endPoint,
IServiceProvider provider,
ILoggerFactory factory,
IRetryPolicy policy
) : base(connection, protocol, endPoint, provider, factory, policy) { }
public new Task StopAsync(CancellationToken token = default)
{
throw new NotImplementedException();
}
}
模拟 HubConnection - 初始尝试 - 使用 new 覆盖
public class MockHubConnection : HubConnection
{
public MockHubConnection(
IConnectionFactory connection,
IHubProtocol protocol,
EndPoint endPoint,
IServiceProvider provider,
ILoggerFactory factory,
IRetryPolicy policy
) : base(connection, protocol, endPoint, provider, factory, policy) { }
public new Task StopAsync(CancellationToken token = default)
{
throw new NotImplementedException();
}
}
测试样本
[Fact]
public async Task HubProxy_StopAsync_Invokes_HubConnection_StopAsync()
{
var mockCon = Mock.Of<IConnectionFactory>();
var mockHubProtocol = Mock.Of<IHubProtocol>();
var mockServiceProvider = Mock.Of<IServiceProvider>();
var mockLoggerFactory = Mock.Of<ILoggerFactory>();
var mockRetryPolicy = Mock.Of<IRetryPolicy>();
var mockHubConnection = new MockHubConnection(
mockCon,
mockHubProtocol,
new IPEndPoint(0, 0),
mockServiceProvider,
mockLoggerFactory,
mockRetryPolicy
);
var _client = new HubProxy(mockHubConnection);
await Assert.ThrowsAsync<NotImplementedException>(() => _client.StopAsync());
}
创建包装器 class 背后的整个想法是模拟包装器 class。包装器 class 仅充当您尝试使用的实际 class 的传递,因此它不会有任何自己的逻辑。由于您使用的是最小起订量,因此无需为 HubConnection
创建模拟 class。你会嘲笑 IHubProxy
所以你不使用与 HubConnection
.
var mockHubProxy = new Mock<IHubProxy>();
// Since you're only trying find out if a
// method is called then you would use MOQ's verify method.
mockHubProxy.Verify(x => x.StopAsync());
// If you really want an exception to be thrown then you
// would do the following. Though I don't think this is the way to go.
mockHubProxy.Setup(x => x.StopAsync()).Throws(new NotImplementedException());
这有意义吗?注意这里没有关于 HubConnection
的任何内容。包装器 HubProxy
的全部要点是删除与 HubConnection
相关的所有内容,因为它无法测试。在 HubProxy
的实际实现中,您的 StopAsync
方法将调用 HubConnection.StopAsync()
的具体实现。在你的模拟中它什么都不做。它只是确保它被调用。
我不知道 HubConnection
的方法,所以我将在下面的示例中使用虚构的方法名称来说明如何为实际具有 [=36= 的方法实现包装器] 值。
public interface IHubProxy
{
string GetStringValue();
}
public HubProxy: IHubProxy
{
private readonly HubConnection _connection;
public HubProxy()
{
_connection = new HubConnection();
}
public string GetStringValue()
{
return _connection.GetStringValue();
}
}
你的 mock 看起来像这样
var mockHubProxy = new Mock<IHubProxy>();
mockHubProxy.Setup(x => x.GetStringValue()).Returns("expected string value")