首页 > 解决方案 > JsonConverter 在集成测试中不起作用

问题描述

我已启用我的 API 以使用字符串值对枚举进行序列化/反序列化。为此,我已将 JsonStringEnumConverter 添加到我的 API 的 Startup 类中支持的 JsonConverters 列表中:

.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);

然后对 enum 属性进行反序列化,不抛出异常。但是现在,ReadFromJsonAsync 只知道JsonStringEnumConverterAPI 使用的其他 JSON 转换器(如 Guid 转换器),而不知道

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

谢谢!

标签: .netintegration-testingjsonconvert

解决方案


不幸的是,没有办法开箱即用地实现这一目标。原因是 System.Text.Json API 的“设计者”以令人难以置信的莫名其妙的举动决定将所述 API 设为静态 - 可能是为了模仿众所周知的 Newtonsoft.Json - 但静态 API 当然不能携带和他们一起陈述。有关更多上下文,请参阅修复此糟糕设计的功能请求

实际上,我在该 FR 中提出了一个解决方案,自从我炮制它以来,我已经对其进行了一些调整:

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参数,因为您想使用在类上定义的那些),然后是委托给静态JsonSerializer方法的所述接口的默认实现。将接口映射到实现中Startup.ConfigureServices,而不是调用JsonSerializer需要处理 JSON 的类中的静态方法,而是将 JSON 接口注入这些类并使用它。

鉴于您正在使用HttpContentJsonExtensions,您还需要定义您自己的该扩展类的包装器版本,该扩展类复制其方法签名但将其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
}

痛吗?是的。这是不必要的吗?也是的。傻吗?第三次,是的。但微软就是这样。


推荐阅读