maxkagamine / Moq.Contrib.HttpClient

A set of extension methods for mocking HttpClient and IHttpClientFactory with Moq.
MIT License
188 stars 11 forks source link

Issues with 'ReturnsJsonResponse' and Deserializing the Response-Content #16

Open ckuetbach opened 1 year ago

ckuetbach commented 1 year ago

If a Https calls Content.ReadAsStreamAsync() to get the Content and deserialize that Value, the returned object only contains null-values.

I created a Unit Test for this Issue:

  ```
  [Fact]
  public async Task RespondsWithJson_NoExtension()
  {
      // Once again using our music API from MatchesCustomPredicate, this time fetching songs
      var expected = new List<Song>()
      {
          new Song()
          {
              Title = "Lost One's Weeping",
              Artist = "Neru feat. Kagamine Rin",
              Album = "世界征服",
              Url = "https://youtu.be/mF4KTG4c-Ic"
          },
          new Song()
          {
              Title = "Gimme×Gimme",
              Artist = "八王子P, Giga feat. Hatsune Miku, Kagamine Rin",
              Album = "Hatsune Miku Magical Mirai 2020",
              Url = "https://youtu.be/IfEAtKW2qSI"
          }
      };

      handler.SetupRequest(HttpMethod.Get, "https://example.com/api/songs").ReturnsJsonResponse(expected);

      // Following line works as expected
      // handler.SetupRequest(HttpMethod.Get, "https://example.com/api/songs").ReturnsJsonResponse(expected, new JsonSerializerOptions());

      var actualMessage = await client.GetAsync("api/songs");
      var actualBody = await actualMessage.Content.ReadAsStreamAsync();
      var actual = await JsonSerializer.DeserializeAsync<List<Song>>(actualBody);

      actual.Should().BeEquivalentTo(expected);
  }

```

If I call .ReturnsJsonResponse(HttpStatusCode.OK, expectedResponse, new JsonSerializerOptions()); and provide any JsonSerializerOptions, the tests works s expected.

I'm not sure, if this is an actual error or a misleading documentation or ReturnsJsonResponse

maxkagamine commented 1 year ago

Hey Christian. This is actually just an unfortunate difference between System.Text.Json and System.Net.Http. The latter uses System.Text.Json's "web" defaults, which camel-cases property names.

ReturnsJsonResponse uses System.Net.Http.Json.JsonContent internally, so unless you specify your own JsonSerializerOptions, it'll end up camel casing, which is fine if you're also using the extension methods from System.Net.Http, like GetFromJsonAsync(), but if you use System.Text.Json directly to deserialize the JsonContent, you'll need to either reset the serializer options back to System.Text.Json's defaults as you did or specify the web defaults when deserializing, like so:

var actual = await JsonSerializer.DeserializeAsync<List<Song>>(actualBody,
    new JsonSerializerOptions(JsonSerializerDefaults.Web));
ckuetbach commented 1 year ago

Hi Max, thanks for you reply.

That makes totally sense. I usually worked with newtonsoft json and its deserializer was case-insensitive.

I'm wondering why the web-defauls are Case-Insensitive // PropertyNameCaseInsensitive: True and it is still an issue: