JsonConverter 在集成测试中不起作用

JsonConverter not working in integration tests

我已经使用字符串值启用 API 到 serialize/deserialize 枚举。为此,我已将 JsonStringEnumConverter 添加到 API 的 Startup class:

支持的 JsonConverter 列表中
.AddJsonOptions(opts =>
{
    var enumConverter = new JsonStringEnumConverter();
    opts.JsonSerializerOptions.Converters.Add(enumConverter);
});

工作正常 - 我的 API 成功地将枚举序列化和反序列化为字符串。

现在 - 我正在尝试为我的 API 构建集成测试并遇到一些问题。 我正在使用 HttpContentJsonExtensions.ReadFromJsonAsync 反序列化 API 响应,但是在枚举 属性.

上抛出了异常

问题很明显- HttpContentJsonExtensions.ReadFromJsonAsync 不知道 API 使用的转换器列表(因为,正如我之前提到的,我已经将 JsonStringEnumConverter 添加到支持的转换器列表,它工作正常)。

如果我在测试函数中这样做:

var options = new System.Text.Json.JsonSerializerOptions();
options.Converters.Add(new JsonStringEnumConverter());
SomeClass result= await response.Content.ReadFromJsonAsync<SomeClass>(options);

然后枚举属性被反序列化,没有抛出异常。但是现在,ReadFromJsonAsync 只知道 JsonStringEnumConverter 而不知道 API 使用的其他 JSON 转换器(如 Guid 转换器)

如何确保 HttpContentJsonExtensions.ReadFromJsonAsync 能够使用 API 使用的所有 JSON 转换器?

谢谢!

不幸的是,没有办法开箱即用。原因是 System.Text.Json API 的“设计者”决定以一种令人难以置信的莫名其妙的举动使所述 API 静态化——可能是为了模仿众所周知的 Newtonsoft.Json——但静态 API,当然,不能随身携带状态。有关更多上下文,我 refer you to the feature request to fix this poor design.

我实际上想出了那个 FR 中的 a solution,自从我编造它以来我已经做了一些调整:

public interface IJsonSerializer
{
    JsonSerializerOptions Options { get; }

    Task<T> DeserializeAsync<T>(Stream utf8Json, CancellationToken cancellationToken);

    // other methods elided for brevity
}

public class DefaultJsonSerializer : IJsonSerializer
{
    private JsonSerializerOptions _options;
    public JsonSerializerOptions Options
        => new JsonSerializerOptions(_options); // copy constructor so that callers cannot mutate the options

    public DefaultJsonSerializer(IOptions<JsonSerializerOptions> options)
        => _options = options.Value;

    public async Task<T> DeserializeAsync<T>(Stream utf8Json, CancellationToken cancellationToken = default)
        => await JsonSerializer.DeserializeAsync<T>(utf8Json, Options, cancellationToken);

    // other methods elided for brevity
}

本质上,你定义了一个包装接口,其方法签名与 JsonSerializer 的静态方法签名相同(减去 JsonSerializerOptions 参数,因为你想使用 class 上定义的参数) ,然后是所述接口的默认实现,它委托给静态 JsonSerializer 方法。将接口映射到 Startup.ConfigureServices 中的实现,而不是在需要处理 JSON 的 class 中调用静态 JsonSerializer 方法,而是注入 JSON进入那些 classes 并使用它。

鉴于您正在使用 HttpContentJsonExtensions,您还需要定义您自己的扩展 class 的包装版本,该版本复制其方法签名但将其 JsonSerializerOptions 参数替换为JSON 序列化接口的实例,然后将所述接口的选项传递给底层 HttpContentJsonExtensions 实现:

public static class IJsonSerializerHttpContentJsonExtensions
{
    public static Task<object?> ReadFromJsonAsync(this HttpContent content, Type type, IJsonSerializer serializer, CancellationToken cancellationToken = default)
        => HttpContentJsonExtensions.ReadFromJsonAsync(content, type, serializer.Options, cancellationToken);

    // other methods elided for brevity
}

痛吗?是的。没必要吗?也是的。愚蠢吗?第三次,是的。但这就是微软。

我遇到了与 OP 描述的完全相同的问题,我的代码看起来非常相似,除了我有 Enum 和 DateOnly 转换器。我用的是System.Text.Json类。对我来说,问题是我需要在序列化选项上更改另一个 属性。我以前只有这个:

// Setup custom serializers
var serializerOptions = new JsonSerializerOptions
{
   Converters = { new Data.DateOnlyConverter(), new Data.EnumStatusConverter() }
};

我需要将以下内容添加到 serializerOptions 对象中:

serializerOptions.PropertyNameCaseInsensitive = true;

然后我的测试可以反序列化 PostAsAsync 函数中的值。