在单元测试中获取配置

问题描述 投票:0回答:1

我想不通如何按照IOptions模式来获取一些我正在使用的配置。

我想测试的是下一个服务。

        private readonly Dictionary<ErrorType, ErrorObject> _errors;

        public UserService(
            ...(Many services)...
            IOptions<Dictionary<ErrorType, ErrorObject>> errors)
        {
            ...
            _errors = errors.Value;
        }

其中ErrorType是一个Enum,ErrorObject是一个对象,格式是我的appsettings. json中的错误。就是这样。

{
  "Errors": {
    "UserNotFound": {
      "ErrorCode": 101,
      "Message": "User not found"
    },
    "WrongPassword": {
      "ErrorCode": 102,
      "Message": "Wrong password"
    }
  }
}

所以这一切都很好,但我现在的问题是我不知道如何实例化它,模拟它,或者在我的测试类中注入它,因为即使我注入它,我也不能在这样的类中解决它,我总是得到一个null。 我在测试夹具中这样注入它(和我真正的注入模块一样)。

serviceCollection.Configure<Dictionary<BusinessErrorType, BusinessErrorObject>>(x => Configuration.GetSection("Errors").Bind(x));

我正确地注入了IConfiguration,并出现了所有的错误,我只是无法为构造函数创建所需的Dictionary。

在这里我没有任何问题地得到了错误。

IConfiguration Configuration = ioCModule.ServiceProvider.GetRequiredService<IConfiguration>();
            var errorsConfig = Configuration.GetSection("Errors");

现在我得想办法在这个声明中加入什么:

var _options = Options.Create<Dictionary<BusinessErrorType, BusinessErrorObject>>();
c# xunit options appsettings
1个回答
3
投票

我在测试夹具中是这样注入的(和我真正的注入模块一样)。

你试图注入一个Dictionary, 而你的类希望得到Options. 试着注册并注入Options,很可能会成功。

现在我得想想在这个声明中放什么。

Options object只是一个随时可以使用的值的持有者,什么都不是。你必须在声明中构建 Options 对象,要么给它一个值,要么什么都不给(最后得到的是空的选项对象)。如果你使用 Options.Create<Dict>() 没有任何参数,你创建一个空对象,就这样。只要在这之前创建字典,然后把它传给Optionsfacotry就可以了。

var dict = new Dictionary<BusinessErrorType, BusinessErrorObject>();
dict.Add("UserNotFound", new BusinessErrorObject(....));
dict.Add("WrongPassword", new BusinessErrorObject(....));

var _options = Options.Create<Dictionary<BusinessErrorType, BusinessErrorObject>>(dict);
// now you have it: _options with some data inside

如果你指望使用Bind()和Options--我怀疑它是否能像那样工作,因为Bind是用来用从设置文件中读取的配置来填充数据结构的,而Options只是一个shell,类似于 "Nullable<>",它的存在只是为了传递东西......。不过我想你可以先创建一个字典,然后用配置来填充它(比如Bind),然后用Options<>包装刚刚填充的字典,然后在IoC中注册Options对象,这样你的UserService就可以得到它的配置。然而,我并不是100%确定Bind是否足够智能来填充通用字典。你需要自己测试一下,可惜我手头没有任何aspnetcore项目可以玩。

编者注。

我有点惊讶你把整个物联网项目设置在了一个地方。单元测试. 当创建一个单元测试时,你要尽可能地切断依赖关系。这意味着,不要使用IoC,因为它可能会给测试带来另一系列的问题,如失败等。

我的意思是,我希望你的单元测试看起来像。

[Test]
public void FoobarizingTheBaz()
{
    var mock1 = ....;
    var mock2 = ....;
    var mock3 = ....;
    var ... = ....;
    var mockN = ....;

    var dict = new Dictionary<....>();
    dict.Add(....);
    dict.Add(....);
    var mockOptions = Options.Create<...>(dict);

    var tested = new UserService(
        mock1, mock2, mock3, ... mockN, mockOptions
    );

    var result = tested.FoobarizeTheBaz();

    // assert...
}

这是一个单元测试 只有被测试的对象被实例化,其余的被模拟,你只提供绝对必要的东西。当然,我不得不写了大量的setupmocksetc,而不是依靠IoC来自动实现它们,但是一旦写好了mock,你就可以把它们拿出来给一些常用的代码,测试类init,夹具等等,然后重用它们。

我的意思是,没有appsettings.json。没有IConfiguration。没有IoC等。

如果你反而想测试很多组件之间的交互,那么它更像是一个集成测试。在这种情况下,你可能想看看 本文 其中涉及到在集成测试设置中使用自定义appsettings.json和IOptions。然而,请注意,在aspnetcore中,"集成测试 "通常意味着测试你的服务如何响应请求和检查响应,所以你将在本文中看到这些。尽管如此,关于IOptions的部分应该是有帮助的。

© www.soinside.com 2019 - 2024. All rights reserved.