{"id":19099735,"url":"https://github.com/ardalis/HttpClientTestExtensions","last_synced_at":"2025-04-18T17:31:52.536Z","repository":{"id":47510441,"uuid":"337539650","full_name":"ardalis/HttpClientTestExtensions","owner":"ardalis","description":"Extensions for testing HTTP endpoints and deserializing the results. Currently works with XUnit.","archived":false,"fork":false,"pushed_at":"2023-09-19T18:41:09.000Z","size":154,"stargazers_count":94,"open_issues_count":2,"forks_count":19,"subscribers_count":6,"default_branch":"main","last_synced_at":"2024-05-01T22:58:22.029Z","etag":null,"topics":["api","asp-net-core","csharp","dotnet","hacktoberfest","testing","web-api"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ardalis.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-02-09T21:21:43.000Z","updated_at":"2024-04-26T05:06:25.000Z","dependencies_parsed_at":"2024-11-05T14:29:33.656Z","dependency_job_id":null,"html_url":"https://github.com/ardalis/HttpClientTestExtensions","commit_stats":{"total_commits":63,"total_committers":4,"mean_commits":15.75,"dds":0.5079365079365079,"last_synced_commit":"a74d6300f81619a455b794288882475a6b0377b0"},"previous_names":[],"tags_count":4,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ardalis%2FHttpClientTestExtensions","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ardalis%2FHttpClientTestExtensions/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ardalis%2FHttpClientTestExtensions/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ardalis%2FHttpClientTestExtensions/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ardalis","download_url":"https://codeload.github.com/ardalis/HttpClientTestExtensions/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":223783108,"owners_count":17201903,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["api","asp-net-core","csharp","dotnet","hacktoberfest","testing","web-api"],"created_at":"2024-11-09T03:52:14.261Z","updated_at":"2024-11-09T03:52:16.525Z","avatar_url":"https://github.com/ardalis.png","language":"C#","readme":"[![.NET Build and Test](https://github.com/ardalis/HttpClientTestExtensions/workflows/.NET%20Build%20and%20Test/badge.svg)](https://github.com/ardalis/HttpClientTestExtensions/actions?query=workflow%3A%22.NET+Build+and+Test%22)\n[![Nuget](https://img.shields.io/nuget/v/Ardalis.HttpClientTestExtensions)](https://www.nuget.org/packages/Ardalis.HttpClientTestExtensions/)\n[![Nuget](https://img.shields.io/nuget/dt/Ardalis.HttpClientTestExtensions)](https://www.nuget.org/packages/Ardalis.HttpClientTestExtensions/)\n\n# HttpClient Test Extensions\n\nExtensions for testing HTTP endpoints and deserializing the results. Currently works with XUnit.\n\n## Installation\n\nAdd the NuGet package:\n\n```powershell\ndotnet add package Ardalis.HttpClientTestExtensions\n```\n\nIn your tests add this namespace:\n\n```csharp\nusing Ardalis.HttpClientTestExtensions;\n```\n\n## Usage\n\nIf you have existing test code that looks something like this:\n\n```csharp\npublic class DoctorsList : IClassFixture\u003cCustomWebApplicationFactory\u003cStartup\u003e\u003e\n{\n  private readonly HttpClient _client;\n  private readonly ITestOutputHelper _outputHelper;\n\n  public DoctorsList(CustomWebApplicationFactory\u003cStartup\u003e factory,\n    ITestOutputHelper outputHelper)\n  {\n    _client = factory.CreateClient();\n    _outputHelper = outputHelper;\n  }\n\n  [Fact]\n  public async Task Returns3Doctors()\n  {\n    var response = await _client.GetAsync(\"/api/doctors\");\n    response.EnsureSuccessStatusCode();\n    var stringResponse = await response.Content.ReadAsStringAsync();\n    _outputHelper.WriteLine(stringResponse);\n    var result = JsonSerializer.Deserialize\u003cListDoctorResponse\u003e(stringResponse,\n      Constants.DefaultJsonOptions);\n\n    Assert.Equal(3, result.Doctors.Count());\n    Assert.Contains(result.Doctors, x =\u003e x.Name == \"Dr. Smith\");\n  }\n}\n```\n\nYou can now update the test to eliminate all but one of the lines prior to the assertions:\n\n```csharp\n[Fact]\npublic async Task Returns3Doctors()\n{\n  var result = await _client.GetAndDeserialize\u003cListDoctorResponse\u003e(\"/api/doctors\", _outputHelper);\n\n  Assert.Equal(3, result.Doctors.Count());\n  Assert.Contains(result.Doctors, x =\u003e x.Name == \"Dr. Smith\");\n}\n```\n\nIf you need to verify an endpoint returns a 404, you can use this approach:\n\n```csharp\n[Fact]\npublic async Task ReturnsNotFoundGivenInvalidAuthorId()\n{\n  int invalidId = 9999;\n\n  var response = await _client.GetAsync(Routes.Authors.Get(invalidId));\n\n  response.EnsureNotFound();\n}\n```\n\n## List of Included Helper Methods\n\n### HttpClient\n\nAll of these methods are extensions on `HttpClient`; the following samples assume `client` is an `HttpClient`. All methods take an optional `ITestOutputHelper`, which is an xUnit type.\n\n#### [GET](src/Ardalis.HttpClientTestExtensions/HttpClientGetExtensionMethods.cs)\n\n```csharp\n// GET and return an object T\nAuthorDto result = await client.GetAndDeserializeAsync(\"/authors/1\", _testOutputHelper);\n\n// GET and return response as a string\nstring result = client.GetAndReturnStringAsync(\"/healthcheck\");\n\n// GET and ensure response contains a substring\nstring result = client.GetAndEnsureSubstringAsync(\"/healthcheck\", \"OMG!\");\n\n// GET and assert a 302 is returned\nvar client = _factory.CreateClient(new WebApplicationFactoryClientOptions() { AllowAutoRedirect = false });\nawait client.GetAndEnsureRedirectAsync(\"/oldone, \"/newone\");\n\n// GET and assert a 400 is returned\nawait client.GetAndEnsureBadRequestAsync(\"/authors?page\");\n\n// GET and assert a 401 is returned\nawait client.GetAndEnsureUnauthorizedAsync(\"/authors/1\");\n\n// GET and assert a 403 is returned\nawait client.GetAndEnsureForbiddenAsync(\"/authors/1\");\n\n// GET and assert a 404 is returned\nawait client.GetAndEnsureNotFoundAsync(\"/authors/-1\");\n```\n\n#### [POST](src/Ardalis.HttpClientTestExtensions/HttpClientPostExtensionMethods.cs)\n\n```csharp\n// NOTE: There's a helper for this now, too (see below)\nvar content = new StringContent(JsonSerializer.Serialize(dto), Encoding.UTF8, \"application/json\");\n\n// POST and return an object T\nAuthorDto result = await client.PostAndDeserializeAsync(\"/authors\", content);\n\n// POST and ensure response contains a substring\nstring result = client.PostAndEnsureSubstringAsync(\"/authors\", content, \"OMG!\");\n\n// POST and assert a 302 is returned\nvar client = _factory.CreateClient(new WebApplicationFactoryClientOptions() { AllowAutoRedirect = false });\nawait client.PostAndEnsureRedirectAsync(\"/oldone\", content, \"/newone\");\n\n// POST and assert a 400 is returned\nawait client.PostAndEnsureBadRequestAsync(\"/authors\", \"banana\");\n\n// POST and assert a 401 is returned\nawait client.PostAndEnsureUnauthorizedAsync(\"/authors\", content);\n\n// POST and assert a 403 is returned\nawait client.PostAndEnsureForbiddenAsync(\"/authors\", content);\n\n// POST and assert a 404 is returned\nawait client.PostAndEnsureNotFoundAsync(\"/wrongendpoint\", content)\n```\n\n#### [PUT](src/Ardalis.HttpClientTestExtensions/HttpClientPutExtensionMethods.cs)\n\n```csharp\nvar content = new StringContent(JsonSerializer.Serialize(dto), Encoding.UTF8, \"application/json\");\n\n// PUT and return an object T\nAuthorDto result = await client.PutAndDeserializeAsync(\"/authors/1\", content);\n\n// PUT and ensure response contains a substring\nstring result = client.PutAndEnsureSubstringAsync(\"/authors/1\", content, \"OMG!\");\n\n// PUT and assert a 302 is returned\nvar client = _factory.CreateClient(new WebApplicationFactoryClientOptions() { AllowAutoRedirect = false });\nawait client.PutAndEnsureRedirectAsync(\"/oldone\", content, \"/newone\");\n\n// PUT and assert a 400 is returned\nawait client.PutAndEnsureBadRequestAsync(\"/authors/1\", \"banana\");\n\n// PUT and assert a 401 is returned\nawait client.PutAndEnsureUnauthorizedAsync(\"/authors/1\", content);\n\n// PUT and assert a 403 is returned\nawait client.PutAndEnsureForbiddenAsync(\"/authors/1\", content);\n\n// PUT and assert a 404 is returned\nawait client.PutAndEnsureNotFoundAsync(\"/wrongendpoint\", content)\n```\n\n#### [PATCH](src\\Ardalis.HttpClientTestExtensions\\HttpClientPatchExtensionMethods.cs)\n\n```csharp\nvar content = new StringContent(JsonSerializer.Serialize(dto), Encoding.UTF8, \"application/json\");\n\n// PATCH and return an object T\nAuthorDto result = await client.PatchAndDeserializeAsync(\"/authors/1\", content);\n\n// PATCH and ensure response contains a substring\nstring result = client.PatchAndEnsureSubstringAsync(\"/authors/1\", content, \"OMG!\");\n\n// PATCH and assert a 302 is returned\nvar client = _factory.CreateClient(new WebApplicationFactoryClientOptions() { AllowAutoRedirect = false });\nawait client.PatchAndEnsureRedirectAsync(\"/oldone\", content, \"/newone\");\n\n// PATCH and assert a 400 is returned\nawait client.PatchAndEnsureBadRequestAsync(\"/authors/1\", \"banana\");\n\n// PATCH and assert a 401 is returned\nawait client.PatchAndEnsureUnauthorizedAsync(\"/authors/1\", content);\n\n// PATCH and assert a 403 is returned\nawait client.PatchAndEnsureForbiddenAsync(\"/authors/1\", content);\n\n// PATCH and assert a 404 is returned\nawait client.PatchAndEnsureNotFoundAsync(\"/wrongendpoint\", content)\n```\n\n#### [DELETE](src\\Ardalis.HttpClientTestExtensions\\HttpClientDeleteExtensionMethods.cs)\n\n```csharp\n// DELETE and return an object T\nAuthorDto result = await client.DeleteAndDeserializeAsync(\"/authors/1\");\n\n// DELETE and ensure response contains a substring\nstring result = client.DeleteAndEnsureSubstringAsync(\"/authors/1\", \"OMG!\");\n\n// DELETE and assert a 204 is returned\nawait client.DeleteAndEnsureNoContentAsync(\"/authors/1\");\n\n// DELETE and assert a 302 is returned\nvar client = _factory.CreateClient(new WebApplicationFactoryClientOptions() { AllowAutoRedirect = false });\nawait client.DeleteAndEnsureRedirectAsync(\"/oldone\", \"/newone\");\n\n// DELETE and assert a 400 is returned\nawait client.DeleteAndEnsureBadRequestAsync(\"/authors/1\");\n\n// DELETE and assert a 401 is returned\nawait client.DeleteAndEnsureUnauthorizedAsync(\"/authors/1\");\n\n// DELETE and assert a 403 is returned\nawait client.DeleteAndEnsureForbiddenAsync(\"/authors/1\");\n\n// DELETE and assert a 404 is returned\nawait client.DeleteAndEnsureNotFoundAsync(\"/wrongendpoint\");\n```\n\n### [HttpResponseMessage](src/Ardalis.HttpClientTestExtensions/HttpResponseMessageExtensionMethods.cs)\n\nAll of these methods are extensions on `HttpResponseMessage`.\n\n```csharp\n// Assert a response has a status code of 204\nresponse.EnsureNoContent();\n\n// Assert a response has a status code of 302\nresponse.EnsureRedirect(\"/newone\");\n\n// Assert a response has a status code of 400\nresponse.EnsureBadRequest();\n\n// Assert a response has a status code of 401\nresponse.EnsureUnauthorized();\n\n// Assert a response has a status code of 403\nresponse.EnsureForbidden();\n\n// Assert a response has a status code of 404\nresponse.EnsureNotFound();\n\n// Assert a response has a given status code\nresponse.Ensure(HttpStatusCode.Created);\n\n// Assert a response contains a substing\nresponse.EnsureContainsAsync(\"OMG!\", _testOutputHelper);\n```\n\n### [StringContentHelpers](src/Ardalis.HttpClientTestExtensions/StringContentHelpers.cs)\n\nExtensions on `HttpContent` which you'll typically want to return a `StringContent` type as you serialize your DTO to JSON.\n\n```csharp\n\n// Convert a C# DTO to a StringContent JSON type\nvar authorDto = new (\"Steve\");\nvar content = StringContentHelpers.FromModelAsJson(authorDto);\n\n// now you can use this with a POST, PUT, etc.\nAuthorDto result = await client.PostAndDeserializeAsync(\"/authors\", content);\n\n// Or you can do it all in one line (assuming you already have the DTO)\nAuthorDto result = await client.PostAndDeserializeAsync(\"/authors\",\n    StringContentHelpers.FromModelAsJson(authorDto));\n```\n\n## Notes\n\n- For now this is coupled with xUnit but if there is interest it could be split so the ITestOutputHelper dependency is removed/optional/swappable\n- Additional helpers for other verbs are planned\n- This is using System.Text.Json with default camelCase options that I've found most useful in my projects. This could be made extensible somehow as well.\n- When making updates to this file make sure to also update the docs/README file that is embedded in the NuGet package","funding_links":[],"categories":["testing"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fardalis%2FHttpClientTestExtensions","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fardalis%2FHttpClientTestExtensions","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fardalis%2FHttpClientTestExtensions/lists"}