如何在 ASP.NET Core 3.0 中编写涉及 DateTime Json 序列化的集成测试?

How to write integration tests involving DateTime Json serialization in ASP.NET Core 3.0?

我正在为 ASP.NET Core 3.0 中的 Api 控制器编写集成测试。该测试针对以实体列表响应的路由。当我尝试对响应内容进行断言时,DateTime 属性的序列化方式存在差异。

我尝试在测试中使用自定义 JsonConverter:

    public class DateTimeConverter : JsonConverter<DateTime>
    {
        public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        {
            return DateTime.Parse(reader.GetString());
        }

        public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)
        {
            writer.WriteStringValue(value.ToString("yyyy-MM-ddThh:mm:ss.ffffff"));
        }
    }

问题在于此转换器不会截断尾随零,而实际响应会截断。因此,测试失败的几率为十分之一。

这是失败的测试:

    [Fact]
    public async Task GetUsers()
    {
        using var clientFactory = new ApplicationFactory<Startup>();
        using var client = clientFactory.CreateClient();
        using var context = clientFactory.CreateContext();

        var user1 = context.Users.Add(new User()).Entity;
        var user2 = context.Users.Add(new User()).Entity;
        context.SaveChanges();

        var users = new List<User> { user1, user2 };
        var jsonSerializerOptions = new JsonSerializerOptions
        {
            PropertyNamingPolicy = JsonNamingPolicy.CamelCase
        };
        var serializedUsers = JsonSerializer.Serialize(users, jsonSerializerOptions);

        var response = await client.GetAsync("/users");

        var responseBody = await response.Content.ReadAsStringAsync();
        Assert.Equal(serializedUsers, responseBody);
        Assert.Equal(HttpStatusCode.OK, response.StatusCode);
    }

我原以为测试会通过,但我却收到了这个错误:

  Error Message:
   Assert.Equal() Failure
                                 ↓ (pos 85)
Expected: ···1-05T22:14:13.242771-03:00","updatedAt"···
Actual:   ···1-05T22:14:13.242771","updatedAt"···

我没有在控制器的实际实现中配置任何序列化选项。

如何正确实施此集成测试?有没有直接的方法在测试中使用与真实控制器相同的选项序列化列表?

1。使用 UTC 时间戳

我强烈建议您使用 UTC 时间戳。

var x = new { UpdatedAtUtc = DateTime.UtcNow };

Console.WriteLine(JsonSerializer.Serialize(x));

产生

{"UpdatedAtUtc":"2019-11-06T02:41:45.4610928Z"}

2。使用您的转换器

var x = new { UpdatedAt = DateTime.Now };

JsonSerializerOptions options = new JsonSerializerOptions();
options.Converters.Add(new DateTimeConverter());

Console.WriteLine(JsonSerializer.Serialize(x, options));
{"UpdatedAt":"2019-11-06T12:50:48.711255"}

3。使用 DateTimeKind.Unspecified

class X { public DateTime UpdatedAt {get;set;}}

public static void Main()
{
    var localNow = DateTime.Now;
    var x = new X{ UpdatedAt = localNow };

    Console.WriteLine(JsonSerializer.Serialize(x));
    x.UpdatedAt = DateTime.SpecifyKind(localNow, DateTimeKind.Unspecified);
    Console.WriteLine(JsonSerializer.Serialize(x));

产生

{"UpdatedAt":"2019-11-06T12:33:56.2598121+10:00"}
{"UpdatedAt":"2019-11-06T12:33:56.2598121"}

顺便说一句。您应该在测试和测试代码中使用相同的 Json 选项。


关于微秒的注释和 DateTimeKind

随着您进行更多测试,您可能会发现放入数据库的对象与从数据库检索的对象之间的时间戳不匹配。

根据您的设置,DateTimes 可能会作为 LocalUnspecified 从数据库中检索(即使您将 Utc 放入数据库)并且您可能会丢失一些精度(db 列将只存储它的最大分辨率,它可能是毫秒)。

你想要做的是获取 DateTime "Round-trip" 表示,这可以通过使用来完成:

https://docs.microsoft.com/en-us/dotnet/standard/base-types/standard-date-and-time-format-strings#Roundtrip

var d = DateTime.Now.ToString("o");

如果您仍然发现使两种 DateTime 格式相同的问题,那么您可以使用 "System.DateTime.Kind" 属性.

https://docs.microsoft.com/en-us/dotnet/api/system.datetime.kind?view=netframework-4.8

这是我做的一个简单的例子,你可以在线运行它:

https://dotnetfiddle.net/ccGSEO