c# - 单元测试类中的依赖注入 IConfiguration
问题描述
我正在使用 ASP.NET Core 3,在对此处看到的具体实现进行单元测试时,我有一个包含 2 个依赖项 IConfiguration 和 ILogger 的帮助程序类:
public class Helper : IHelpers
{
private IConfiguration configuration;
private readonly ILogger<Helper> logger;
public Helper(IConfiguration _config, ILogger<Helper> _logger)
{
configuration = _config;
logger = _logger;
}
public Tuple<string, string> SpotifyClientInformation()
{
Tuple<string, string> tuple = null;
try
{
if (configuration != null)
{
string clientID = configuration["SpotifySecrets:clientID"];
//Todo move secretID to more secure location
string secretID = configuration["SpotifySecrets:secretID"];
tuple = new Tuple<string, string>(clientID, secretID);
}
}
catch (Exception ex)
{
logger.LogCritical("Configuration DI not set up correctly",ex);
}
return tuple;
}
}
在我的测试中,我尝试通过以下代码使用 DI 助手:
public class HelperTests
{
private ServiceCollection serviceCollection;
private IHelpers helper;
[SetUp]
public void Setup()
{
serviceCollection = new ServiceCollection();
serviceCollection.AddTransient<IHelpers, Helper>();
ServiceProvider serviceProvider = serviceCollection.BuildServiceProvider();
helper = serviceProvider.GetService<IHelpers>();
}
[Test]
public void GetSpotifyConfiguration()
{
Tuple<string, string> devData = helper.SpotifyClientInformation();
Assert.NotNull(devData.Item1);
Assert.NotNull(devData.Item2);
}
}
但我收到错误消息:
System.InvalidOperationException : Unable to resolve service for type
'Microsoft.Extensions.Configuration.IConfiguration' while attempting to activate 'Spotify_Angular.Helper'.
解决方案
由于您尚未在设置中使用 DI 容器注册配置或记录器接口,因此您遇到了异常。你可以这样做,但我可以建议一个替代方案。
与其创建具体的实现并注册它们/创建服务提供者,不如模拟依赖项。它将使这个过程变得更加容易,并且在你应该如何编写单元测试方面更加规范。
包括对 Moq 的引用,然后对于成功案例执行以下操作:
[Test]
public void SpotifyClientInformation_ReturnsExpectedClientIdAndSecretIdTuple()
{
var logger = Mock.Of<ILogger<Helper>>();
var expectedClientId = "My client Id";
var expectedSecretId = "My secret Id";
var configurationMock = new Mock<IConfiguration>();
configurationMock.Setup(m => m["SpotifySecrets:clientID"]).Returns(expectedClientId);
configurationMock.Setup(m => m["SpotifySecrets:secretID"]).Returns(expectedSecretId);
var configuration = configurationMock.Object;
var helper = new Helper(configuration, logger);
var (actualClientId, actualSecretId) = helper.SpotifyClientInformation();
Assert.Multiple(() =>
{
Assert.That(actualClientId, Is.EqualTo(expectedClientId));
Assert.That(actualSecretId, Is.EqualTo(expectedSecretId));
});
}
安排依赖关系并设置测试状态;通过调用 SpotifyClientInformation 来执行;最后断言结果元组具有预期值。
从那里开始,您可以轻松地增加对空配置案例的覆盖范围
[Test]
public void SpotifyClientInformation_WithNullConfiguration_ReturnsNull()
{
var logger = Mock.Of<ILogger<Helper>>();
var helper = new Helper(null, logger);
var actualResult = helper.SpotifyClientInformation();
Assert.That(actualResult, Is.Null);
}
可以通过另一个测试来断言日志消息,但我不认为这是现实的。您所做的只是从配置实例中读取值并创建元组,我认为两者都不会产生异常。尝试读取不存在的配置键会返回 null。
如果您想使用日志消息捕获该场景,更好的方法是在构造函数中执行此操作
public class Helper : IHelpers
{
private IConfiguration configuration;
private readonly ILogger<Helper> logger;
public Helper(IConfiguration _config, ILogger<Helper> _logger)
{
configuration = _config;
logger = _logger;
if (configuration == null)
{
logger.LogCritical("Configuration DI not set up correctly");
}
}
public Tuple<string, string> SpotifyClientInformation()
{
if (configuration == null)
{
return null;
}
string clientID = configuration["SpotifySecrets:clientID"];
//Todo move secretID to more secure location
string secretID = configuration["SpotifySecrets:secretID"];
return new Tuple<string, string>(clientID, secretID);
}
}
然后如果你想断言日志消息
[Test]
public void Initialize_NullConfiguration_GeneratesCriticalLog()
{
var expectedLogMessage = "Configuration DI not set up correctly";
var logger = Mock.Of<ILogger<Helper>>();
var helper = new Helper(null, logger);
Mock.Get(logger).Verify(m => m.Log(
LogLevel.Critical,
It.IsAny<EventId>(),
It.Is<It.IsAnyType>((o, t) => HasLogMessage(o, expectedLogMessage)),
It.IsAny<KeyNotFoundException>(),
(Func<It.IsAnyType, Exception, string>) It.IsAny<object>()),
Times.Once
);
}
private static bool HasLogMessage(object state, string expectedMessage)
{
var loggedValues = (IReadOnlyList<KeyValuePair<string, object>>) state;
var unformattedMessage = loggedValues[^1].Value.ToString();
return unformattedMessage.Equals(expectedMessage, StringComparison.CurrentCultureIgnoreCase);
}
就我个人而言,我不担心像记录器和配置这样的空值保护 DI 主食。如果必须的话,我会提出一个 ArgumentNullException 以真正将其归为该类不能/不应该在没有它的情况下运行,并且我应该在部署之前修复 DI 配置。这样做意味着您可以取消SpotifyClientInformation
方法中的空检查以及对消耗块的任何空检查SpotifyClientInformation
。
推荐阅读
- c++ - 将原始指针从较大的对象传递到较小的对象以做专门的事情的正确方法是什么?
- azure - 使用 Python 设置 Azure Active Directory SAML
- html - “d-none 和 d-sm-block” 在段落的所需区域不起作用
- dynamics-crm - Dynamics Field Service Mobile 深层链接未导航到实体
- r - 使用 groupby 另一列填充缺失的日期
- mongodb - 我不明白其他人如何在我的 mongodb 中制作集合
- php - 无法通过 php api 登录到 ftp 服务器,但通过 FileZilla 可以正常工作
- react-native - 在 React Native 中关闭应用程序时如何触发 UI 操作?
- api - grafana- 从仪表板获取值,使用 mysql 作为 API 的数据源
- c# - 使用针对 signalR 端点的不同策略覆盖全局 CORS 策略