在单元测试中获取配置

Get configuration in Unit Test

我不知道如何按照 IOptions 模式获取我正在使用的某些配置。

我要测试的服务是下一个:

        private readonly Dictionary<ErrorType, ErrorObject> _errors;

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

其中 ErrorType 是一个枚举和 ErrorObject,一个具有我在 appsettings.json 中的错误格式的对象。就是这样:

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

所以这工作得很好,但我现在的问题是我不知道如何实例化它、模拟它或在我的测试中注入它 class,因为即使我正在注入它我也可以' 以 class 的方式解决它,我总是得到一个空值。 我在测试夹具中像这样注入它(与我的真实注入模块相同):

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

我必须正确地注入 IConfiguration 和所有错误,我只是无法为构造函数创建所需的字典。

在这里我得到了没有任何问题的错误:

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

现在我得弄清楚要在这个声明中放些什么:

var _options = Options.Create<Dictionary<BusinessErrorType, BusinessErrorObject>>();

I injected it like this in the test fixture (same as in my real injection module):

您已尝试注入字典,而您的 class 期望获得选项。尝试注册并注入选项,很可能会成功。

And now I gotta figure out what to put in this declaration:

Options 对象只是一些随时可用的值的持有者,或者 没有什么。你必须构造 Options 对象 它的价值,或者不给它任何东西(并以空选项结束 目的)。如果使用不带任何参数的 Options.Create<Dict>(),则会创建一个空对象,仅此而已。只需在此之前创建字典并将其传递给 Options 工厂.

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<>",它的存在只是为了传递东西......但是我想你可以先创建一个字典,然后用配置填充它(即绑定)然后包装刚刚填写的字典使用 Options<>,然后在 IoC 中注册该 Options 对象,以便您的 UserService 可以获得其配置。但是,我不能 100% 确定 Bind 是否足够智能以填充通用词典。你需要自己测试一下,不幸的是我手头没有任何aspnetcore项目可以玩

EDIT/Sidenote:

我有点惊讶你在 单元测试 中 building/setting 完成了整个 IoC。创建单元测试时,您需要 cut/mock 尽可能多的依赖项。也就是说,没有IoC,可以引入另一组problems/failures/etc来测试。

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

[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...
}

那是一个单元测试。只有被测试的对象被实例化,其余的被模拟,你只提供绝对必要的东西。当然,我不得不写很多 setup/mocks/etc 而不是依赖 IoC 来自动实现它们,但是一旦编写了模拟,你就可以将它们取出一些通用代码,测试 class init 、夹具等并重复使用它们。

我的意思是,没有 appsettings.json。无配置。无 IoC 等

如果您想测试有多少组件相互交互,那么它更像是一个集成测试。在这种情况下,您可能希望看到 this article 涉及在集成测试设置中使用自定义 appsettings.json 和 IOptions。但是,请注意,aspnetcore 中的 "integration tests" 通常意味着测试您的服务如何响应请求并检查响应,因此您将在本文中看到这一点。尽管如此,有关 IOptions 的部分应该有所帮助。