{"id":19992680,"url":"https://github.com/sketch7/FluentlyHttpClient","last_synced_at":"2025-05-04T11:31:41.887Z","repository":{"id":55153326,"uuid":"95261394","full_name":"sketch7/FluentlyHttpClient","owner":"sketch7","description":"Http Client for .NET Standard with fluent APIs which are intuitive, easy to use and also highly extensible.","archived":false,"fork":false,"pushed_at":"2025-03-07T14:33:40.000Z","size":580,"stargazers_count":117,"open_issues_count":1,"forks_count":17,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-04-08T04:13:28.036Z","etag":null,"topics":["csharp","dotnet","fluent-api","fluentapi","http-client","httpclient","middleware","netstandard","rest","rest-client"],"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/sketch7.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":"CODEOWNERS","security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2017-06-23T22:40:18.000Z","updated_at":"2025-03-07T14:32:36.000Z","dependencies_parsed_at":"2024-03-22T14:49:08.663Z","dependency_job_id":"57c1a368-b97a-430c-8a4b-841932973b6b","html_url":"https://github.com/sketch7/FluentlyHttpClient","commit_stats":{"total_commits":137,"total_committers":8,"mean_commits":17.125,"dds":"0.13138686131386856","last_synced_commit":"62b7a96a2628b3cfb6860d3dcb0796a78d554a81"},"previous_names":[],"tags_count":93,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sketch7%2FFluentlyHttpClient","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sketch7%2FFluentlyHttpClient/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sketch7%2FFluentlyHttpClient/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sketch7%2FFluentlyHttpClient/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/sketch7","download_url":"https://codeload.github.com/sketch7/FluentlyHttpClient/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252329577,"owners_count":21730641,"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":["csharp","dotnet","fluent-api","fluentapi","http-client","httpclient","middleware","netstandard","rest","rest-client"],"created_at":"2024-11-13T04:52:16.300Z","updated_at":"2025-05-04T11:31:36.872Z","avatar_url":"https://github.com/sketch7.png","language":"C#","readme":"[projectUri]: https://github.com/sketch7/FluentlyHttpClient\n[projectGit]: https://github.com/sketch7/FluentlyHttpClient.git\n[changeLog]: ./CHANGELOG.md\n\n# Fluently Http Client \u003c!-- omit in toc --\u003e\n[![CI](https://github.com/sketch7/FluentlyHttpClient/actions/workflows/dotnet-publish.yml/badge.svg)](https://github.com/sketch7/FluentlyHttpClient/actions/workflows/dotnet-publish.yml)\n[![NuGet version](https://badge.fury.io/nu/fluentlyhttpclient.svg)](https://badge.fury.io/nu/fluentlyhttpclient)\n\nHttp Client for .NET Standard with fluent APIs which are intuitive, easy to use and also highly extensible.\n\n**Quick links**\n\n[Change logs][changeLog] | [Project Repository][projectUri]\n\n## Features\n- Fluent APIs\n- Highly extensible\n- Middleware Support\n  - Custom Classes with DI enabled\n  - Access to both Request/Response within same scope (similar to ASPNET middleware)\n  - Logger and Timer middleware out of the box\n- Multiple HttpClient support with a Fluent API for Client builder\n- Customizable Formatters (JSON, XML out of the box)\n- Url interpolation and query params e.g. `person/{id}` / `person?id=1`\n- GraphQL support\n- File upload support\n\n## Installation\n\n## dotnet support\n\n| Version | .NET               | Status                                                                                                                                                                                               |\n| ------- | ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| 1.x     | .NET Standard 1.4+ |                                                                                                                                                                                                      |\n| 2.x     | .NET Standard 2    |                                                                                                                                                                                                      |\n| 3.x     | .NET Standard 2    | [![CI](https://github.com/sketch7/FluentlyHttpClient/actions/workflows/dotnet-publish.yml/badge.svg?branch=3.x)](https://github.com/sketch7/FluentlyHttpClient/actions/workflows/dotnet-publish.yml) |\n| 4.x     | net8               | [![CI](https://github.com/sketch7/FluentlyHttpClient/actions/workflows/dotnet-publish.yml/badge.svg?branch=4.x)](https://github.com/sketch7/FluentlyHttpClient/actions/workflows/dotnet-publish.yml) |\n\n### NuGet\n```\nPM\u003e Install-Package FluentlyHttpClient\n```\n\n### csproj\n\n```xml\n\u003cPackageReference Include=\"FluentlyHttpClient\" Version=\"*\" /\u003e\n```\n\n## Table of Contents \u003c!-- omit in toc --\u003e\n- [Features](#features)\n- [Installation](#installation)\n- [dotnet support](#dotnet-support)\n  - [NuGet](#nuget)\n  - [csproj](#csproj)\n- [Usage](#usage)\n  - [Configure](#configure)\n  - [Basic usage](#basic-usage)\n    - [Simple API](#simple-api)\n    - [Fluent Request API](#fluent-request-api)\n  - [Fluent Http Client Builder](#fluent-http-client-builder)\n    - [Register to Factory](#register-to-factory)\n    - [Register multiple + share](#register-multiple--share)\n    - [Create Http Client from Client](#create-http-client-from-client)\n    - [Configure defaults for Http Clients](#configure-defaults-for-http-clients)\n    - [Http Client Builder extra goodies](#http-client-builder-extra-goodies)\n    - [Re-using Http Client from Factory](#re-using-http-client-from-factory)\n  - [Request Builder](#request-builder)\n    - [Usage](#usage-1)\n    - [Query params](#query-params)\n    - [Interpolate Url](#interpolate-url)\n    - [ReturnAsReponse, ReturnAsResponse`\u003cT\u003e` and Return`\u003cT\u003e`](#returnasreponse-returnasresponset-and-returnt)\n  - [GraphQL](#graphql)\n  - [Middleware](#middleware)\n    - [Middleware options](#middleware-options)\n    - [Use a middleware](#use-a-middleware)\n    - [Request/Response items](#requestresponse-items)\n  - [Extending](#extending)\n    - [Extending Request Builder](#extending-request-builder)\n    - [Extending Request Builder/Client Builder headers](#extending-request-builderclient-builder-headers)\n    - [Extending Request/Response items](#extending-requestresponse-items)\n  - [Recipes](#recipes)\n    - [File upload](#file-upload)\n    - [Simple Single file HttpClient](#simple-single-file-httpclient)\n  - [Testing/Mocking](#testingmocking)\n    - [Test example with RichardSzalay.MockHttp](#test-example-with-richardszalaymockhttp)\n- [Contributing](#contributing)\n  - [Setup Machine for Development](#setup-machine-for-development)\n  - [Commands](#commands)\n\n## Usage\n\n### Configure\nAdd services via `.AddFluentlyHttpClient()`.\n\n```cs\n// using Startup.cs (can be elsewhere)\npublic void ConfigureServices(IServiceCollection services)\n{\n    services.AddFluentlyHttpClient();\n}\n```\n\nConfigure an Http client using the Http Factory (you need at least one).\n```cs\n// using Startup.cs (can be elsewhere)\npublic void Configure(IApplicationBuilder app, IFluentHttpClientFactory fluentHttpClientFactory)\n{\n  fluentHttpClientFactory.CreateBuilder(identifier: \"platform\") // keep a note of the identifier, its needed later\n    .WithBaseUrl(\"http://sketch7.com\") // required\n    .WithHeader(\"user-agent\", \"slabs-testify\")\n    .WithTimeout(5)\n    .UseMiddleware\u003cLoggerHttpMiddleware\u003e()\n    .Register(); // register client builder to factory\n}\n```\n\n### Basic usage\n\n#### Simple API\nSimple API (non-fluent) is good for simple requests as it has a clean, minimal API.\n\n```cs\n// inject factory and get client\nvar httpClient = fluentHttpClientFactory.Get(identifier: \"platform\");\n\n// HTTP GET + deserialize result (non-fleunt API)\nHero hero = await httpClient.Get\u003cHero\u003e(\"/api/heroes/azmodan\");\n\n// HTTP POST + deserialize result (non-fleunt API)\nHero hero = await httpClient.Post\u003cHero\u003e(\"/api/heroes/azmodan\", new\n    {\n        Title = \"Lord of Sin\"\n    });\n```\n\n#### Fluent Request API\nFluent request API (request builder) allows to create more complex requests and provides further control on the response.\n\n```cs\n// inject factory and get client\nvar httpClient = fluentHttpClientFactory.Get(identifier: \"platform\");\n\n// HTTP GET + return response and deserialize result (fluent API)\nFluentHttpResponse\u003cHero\u003e response =\n  await httpClient.CreateRequest(\"/api/heroes/azmodan\")\n    .ReturnAsResponse\u003cHero\u003e(); // return with response\n\n// HTTP POST + return response and deserialize result (fluent API)\nHero hero = await httpClient.CreateRequest(\"/api/heroes/azmodan\")\n    .AsPost()\n    .WithBody(new\n    {\n        Title = \"Lord of Sin\"\n    })\n    .Return\u003cHero\u003e(); // return deserialized result directly\n```\n\n### Fluent Http Client Builder\nHttp client builder is used to configure http clients in a fluent way.\n\n#### Register to Factory\n\n```cs\nvar clientBuilder = fluentHttpClientFactory.CreateBuilder(identifier: \"platform\")\n    .WithBaseUrl(\"http://sketch7.com\");\nfluentHttpClientFactory.Add(clientBuilder);\n\n// or similarly via the builder itself.\nclientBuilder.Register().\n```\n\n#### Register multiple + share\nThere are multiple ways how to register multiple http clients. The following is a nice way of doing it:\n\n```cs\nfluentHttpClientFactory.CreateBuilder(\"platform\")\n    // shared\n    .WithHeader(\"user-agent\", \"slabs-testify\")\n    .WithTimeout(5)\n    .UseTimer()\n    .UseMiddleware\u003cLoggerHttpMiddleware\u003e()\n\n    // platform\n    .WithBaseUrl(\"https://platform.com\")\n    .Register()\n\n    // big-data - reuse all above and replace the below\n    .WithIdentifier(\"big-data\")\n    .WithBaseUrl(\"https://api.big-data.com\")\n    .Register();\n```\n\n#### Create Http Client from Client\nIts also possible to create a new http client from an http client, sort of sub-client which inherits options from its creator.\nThis might be good to pass defaults for a specific endpoint.\n\n```cs\nvar httpClient = factory.Get(\"platform\");\nvar paymentsClient = httpClient.CreateClient(\"payments\")\n  .WithHeader(\"X-Gateway\", \"xxx\")\n  .WithTimeout(30)\n  .Build();\n```\n\n#### Configure defaults for Http Clients\nIts also possible to configure builder defaults for all http clients via `ConfigureDefaults` within `IFluentHttpClientFactory`.\nSee example below.\n\n```cs\nfluentHttpClientFactory.ConfigureDefaults(builder =\u003e builder\n  .WithUserAgent(\"sketch7\")\n  .WithTimeout(5)\n);\n\n// or\nservices.AddFluentlyHttpClient(builder =\u003e builder\n  .WithUserAgent(\"sketch7\")\n)\n```\n\n#### Http Client Builder extra goodies\n\n```cs\n// message handler - set HTTP handler stack to use for sending requests\nvar mockHttp = new MockHttpMessageHandler();\nhttpClientBuilder.WithMessageHandler(mockHttp);\n\n// request builder defaults - handler to customize defaults for request builder\nhttpClientBuilder.WithRequestBuilderDefaults(builder =\u003e builder.AsPut());\n\n// formatters - used for content negotiation, for \"Accept\" and body media formats. e.g. JSON, XML, etc...\nhttpClientBuilder.ConfigureFormatters(opts =\u003e\n    {\n      opts.Default = new MessagePackMediaTypeFormatter(); // use messagepack\n      opts.Default = opts.Formatters.SystemTextJsonFormatter(); // use system.text.json\n      opts.Formatters.Add(new CustomFormatter());\n    });\n\nhttpClientBuilder.WithVersion(HttpVersion.Version30) // specify to use http3 (defaults: http2)\n```\n\n#### Re-using Http Client from Factory\nAs a best practice rather than using a string each time for the identifier, it's better\nto create an extension method for it.\n\n```cs\npublic static class FluentHttpClientFactoryExtensions\n{\n    public static IFluentHttpClient GetPlatformClient(this IFluentHttpClientFactory factory)\n        =\u003e factory.Get(\"platform\");\n}\n```\n\n\n### Request Builder\nRequest builder is used to build http requests in a fluent way.\n\n#### Usage\n\n```cs\nLoginResponse loginResponse =\n  await fluentHttpClient.CreateRequest(\"/api/auth/login\")\n    .AsPost() // set as HTTP Post\n    .WithBody(new\n    {\n        Username = \"test\",\n        Password = \"test\"\n    }) // serialize body content\n    .WithSuccessStatus() // ensure response success status\n    .Return\u003cLoginResponse\u003e(); // send, deserialize result and return result directly.\n```\n\n#### Query params\n```cs\nrequestBuilder.WithQueryParams(new\n    {\n        Take = 5,\n        Roles = new List\u003cstring\u003e { \"warrior\", \"assassin\" },\n    }, opts =\u003e {\n        opts.CollectionMode = QueryStringCollectionMode.CommaSeparated;\n        opts.KeyFormatter = key =\u003e key.ToLower();\n    }); // =\u003e /url?roles=warrior,assassin\u0026take=5\n```\n\n#### Interpolate Url\n```cs\nrequestBuilder.WithUri(\"{Language}/heroes/{Hero}\", new\n    {\n        Language = \"en\",\n        Hero = \"azmodan\"\n    }); // =\u003e /en/heroes/azmodan\n```\n\n#### ReturnAsReponse, ReturnAsResponse`\u003cT\u003e` and Return`\u003cT\u003e`\n\n```cs\n// send and returns HTTP response\nFluentHttpResponse response = requestBuilder.ReturnAsResponse();\n\n// send and returns HTTP response + deserialize and return result via `.Data`\nFluentHttpResponse\u003cHero\u003e response = requestBuilder.ReturnAsResponse\u003cHero\u003e();\n\n// send and returns derserialized result directly\nHero hero = requestBuilder.Return\u003cHero\u003e();\n```\n\n\n### GraphQL\nFluentlyHttpClient :heart: GraphQL. First class support for GraphQL to be able to create request/response even simpler.\n\n```cs\n// configure globally to use uri for GraphQL endpoint.\nhttpClientBuilder.WithRequestBuilderDefaults(requestBuilder =\u003e requestBuilder.WithUri(\"api/graphql\"));\n\n// send and returns HTTP response + deserialize and return result via `.Data` directly\nFluentHttpResponse\u003cHero\u003e response =\n  await fluentHttpClient.CreateGqlRequest(\"{ hero {name, title } }\")\n    .ReturnAsGqlResponse\u003cHero\u003e();\n    // =\u003e response.Data.Title\n```\n\n\n### Middleware\nMiddleware's are used to intercept request/response to add additional logic or alter request/response.\n\nImplementing a middleware for the HTTP client is quite straight forward, and it's very similar to\nASP.NET Core middleware.\n\nThese are provided out of the box:\n\n| Middleware | Description                                   |\n| ---------- | --------------------------------------------- |\n| Timer      | Determine how long (timespan) requests takes. |\n| Logger     | Log request/response.                         |\n\nTwo important points to keep in mind:\n - The first argument within constructor has to be `FluentHttpMiddlewareDelegate` which is generally called `next`.\n - The second argument within constructor has to be `FluentHttpMiddlewareClientContext` which is generally called `context`,\n - During `Invoke` the `await _next(context);` must be invoked and return the response, in order to continue the flow.\n\n The following is the timer middleware implementation *(bit simplified)*.\n\n```cs\npublic class TimerHttpMiddleware : IFluentHttpMiddleware\n{\n    private readonly FluentHttpMiddlewareDelegate _next;\n    private readonly TimerHttpMiddlewareOptions _options;\n    private readonly ILogger _logger;\n\n    public TimerHttpMiddleware(\n      FluentHttpMiddlewareDelegate next, // this needs to be here and should be first\n      FluentHttpMiddlewareClientContext context, // this needs to be here and should be second\n      TimerHttpMiddlewareOptions options,\n      ILoggerFactory loggerFactory\n    )\n    {\n        _next = next;\n        _options = options;\n        _logger = loggerFactory.CreateLogger($\"{typeof(TimerHttpMiddleware).Namespace}.{context.Identifier}.Timer\");\n    }\n\n    public async Task\u003cFluentHttpResponse\u003e Invoke(FluentHttpMiddlewareContext context)\n    {\n        var watch = Stopwatch.StartNew();\n        var response = await _next(context); // this needs to be invoked to continue middleware flow\n        var elapsed = watch.Elapsed;\n        _logger.LogInformation(\"Executed request {request} in {timeTakenMillis}ms\", context.Request, elapsed.TotalMilliseconds);\n        response.SetTimeTaken(elapsed);\n        return response;\n    }\n}\n\nnamespace FluentlyHttpClient\n{\n    // Response extension methods - useful to extend FluentHttpResponse\n    public static class TimerFluentResponseExtensions\n    {\n        private const string TimeTakenKey = \"TIMER_TIME_TAKEN\";\n\n        public static void SetTimeTaken(this FluentHttpResponse response, TimeSpan value)\n          =\u003e response.Items.Add(TimeTakenKey, value);\n\n        public static TimeSpan GetTimeTaken(this FluentHttpResponse response)\n          =\u003e (TimeSpan)response.Items[TimeTakenKey];\n    }\n\n    // FluentHttpClientBuilder extension methods - add\n    public static class FluentlyHttpMiddlwareExtensions\n    {\n        public static FluentHttpClientBuilder UseTimer(this FluentHttpClientBuilder builder, TimerHttpMiddlewareOptions options = null)\n            =\u003e builder.UseMiddleware\u003cTimerHttpMiddleware\u003e(options ?? new TimerHttpMiddlewareOptions());\n    }\n}\n\n// response extension usage\nTimeSpan timeTaken = response.GetTimeTaken();\n```\n\n#### Middleware options\nOptions to middleware can be passed via an argument. Note it has to be the second argument within the constructor.\n\n```cs\npublic TimerHttpMiddleware(\n  FluentHttpMiddlewareDelegate next,\n  FluentHttpMiddlewareClientContext context,\n  TimerHttpMiddlewareOptions options, // \u003c- options should be here\n  ILoggerFactory loggerFactory\n)\n```\n\nOptions can be passed when registering a middleware.\n\n#### Use a middleware\n\n```cs\nfluentHttpClientFactory.CreateBuilder(\"platform\")\n    .UseMiddleware\u003cLoggerHttpMiddleware\u003e() // register a middleware (without args)\n    .UseMiddleware\u003cTimerHttpMiddleware\u003e(new TimerHttpMiddlewareOptions\n      {\n          WarnThreshold = TimeSpan.Zero\n      }) // register a middleware with options (args)\n    .UseTimer(new TimerHttpMiddlewareOptions\n      {\n          WarnThreshold = TimeSpan.Zero\n      }) // register a middleware using extension method\n```\nAs a best practice, it's best to provide an extension method for usage such as `UseTimer`\nespecially when it has any arguments (options), as it won't be convenient to use.\n\n\n#### Request/Response items\nWhen using middleware additional data can be added to the request/response via the `.Items` of request/response,\nin order to share state across middleware for the request or to extend response.\n\nThe timer middleware example is making use of it.\n\n```cs\n// set item\nresponse.SetTimeTaken(elapsed);\n\n// or similarly without extension method\nresponse.Items.Add(\"TIME_TAKEN\", value)\n\n// get item\nTimeSpan timeTaken = response.GetTimeTaken();\n\n// or similarly without extension method\nTimeSpan timeTaken = (TimeSpan)response.Items[\"TIME_TAKEN\"];\n```\n\n\n### Extending\nOne of the key features is the ability to extend its own APIs easily.\nIn fact, several functions of the library itself are extensions, by using extension methods.\n\n#### Extending Request Builder\nAn example of how can the request builder be extended.\n\n```cs\npublic static class FluentHttpRequestBuilderExtensions\n{\n    public static FluentHttpRequestBuilder WithBearerAuthentication(this FluentHttpRequestBuilder builder, string token)\n    {\n        if (string.IsNullOrEmpty(token)) throw new ArgumentNullException(nameof(token));\n        builder.WithHeader(\"Authorization\", $\"Bearer {token}\");\n        return builder;\n    }\n}\n```\n\n#### Extending Request Builder/Client Builder headers\nIn order to extend headers for both `FluentHttpClientBuilder` and `FluentHttpRequestBuilder`, the best approach would be to extend on\n`IFluentHttpHeaderBuilder\u003cT\u003e`, this way it will be available for both. See example below.\n\n```cs\npublic static class FluentHttpHeaderBuilderExtensions\n{\n  public static T WithBearerAuthentication\u003cT\u003e(this IFluentHttpHeaderBuilder\u003cT\u003e builder, string token)\n  {\n    if (string.IsNullOrEmpty(token)) throw new ArgumentNullException(nameof(token));\n    builder.WithHeader(HeaderTypes.Authorization, $\"{AuthSchemeTypes.Bearer} {token}\");\n    return (T)builder;\n  }\n```\n#### Extending Request/Response items\nIn order to extend `Items` for both `FluentHttpRequest` and `FluentHttpResponse`, its best to extend `IFluentHttpMessageState`.\nThis way it will be available for both. See example below.\n\n```cs\npublic static IDictionary\u003cstring, string\u003e GetErrorCodeMappings(this IFluentHttpMessageState message)\n{\n  if (message.Items.TryGetValue(ErrorCodeMappingKey, out var value))\n    return (IDictionary\u003cstring, string\u003e)value;\n  return null;\n}\n```\n\n### Recipes\n\n#### File upload\n\n```cs\nvar multiForm = new MultipartFormDataContent\n{\n  { \"hero\", \"Jaina\" }\n};\nmultiForm.AddFile(\"file\", \"./animal-mustache.jpg\");\n\nvar response = await httpClient.CreateRequest(\"/api/sample/upload\")\n  .AsPost()\n  .WithBodyContent(multiForm)\n  .ReturnAsResponse\u003cMyResult\u003e();\n```\n\n#### Simple Single file HttpClient\nEven though in general we do not suggest (unless for small HttpClients) at times its useful to create a simple quick way http client.\n\n```cs\npublic class SelfInfoHttpClient\n{\n  private readonly IFluentHttpClient _httpClient;\n\n  public SelfInfoHttpClient(\n    IFluentHttpClientFactory httpClientFactory\n  )\n  {\n    _httpClient = httpClientFactory.CreateBuilder(\"localhost\")\n      .WithBaseUrl($\"http://localhost:5500}\")\n      .Build();\n  }\n\n  public Task\u003cFluentHttpResponse\u003e GetInfo()\n    =\u003e _httpClient.CreateRequest(\"info\")\n      .AsGet()\n      .ReturnAsResponse();\n}\n```\n\n### Testing/Mocking\nIn order to test HTTP requests, the library itself doesn't offer anything out of the box.\nHowever, we've been using [RichardSzalay.MockHttp](https://github.com/richardszalay/mockhttp), which we recommend.\n\n#### Test example with RichardSzalay.MockHttp\n\n```cs\n[Fact]\npublic async void ShouldReturnContent()\n{\n    // build services\n    var servicesProvider = new ServiceCollection()\n      .AddFluentlyHttpClient()\n      .AddLogging()\n      .BuildServiceProvider();\n    var fluentHttpClientFactory = servicesProvider.GetService\u003cIFluentHttpClientFactory\u003e();\n\n    // define mocks\n    var mockHttp = new MockHttpMessageHandler();\n    mockHttp.When(\"https://sketch7.com/api/heroes/azmodan\")\n      .Respond(\"application/json\", \"{ 'name': 'Azmodan' }\");\n\n    var httpClient = fluentHttpClientFactory.CreateBuilder(\"platform\")\n      .WithBaseUrl(\"https://sketch7.com\")\n      .AddMiddleware\u003cTimerHttpMiddleware\u003e()\n      .WithMessageHandler(mockHttp) // set message handler to mock\n      .Build();\n\n    var response = await httpClient.CreateRequest(\"/api/heroes/azmodan\")\n      .ReturnAsResponse\u003cHero\u003e();\n\n    Assert.NotNull(response.Data);\n    Assert.Equal(\"Azmodan\", response.Data.Name);\n    Assert.NotEqual(TimeSpan.Zero, response.GetTimeTaken());\n}\n```\n\n## Contributing\n\n### Setup Machine for Development\nInstall/setup the following:\n\n- NodeJS v8+\n- Visual Studio Code or similar code editor\n- Git + SourceTree, SmartGit or similar (optional)\n\n ### Commands\n\n```bash\n# run tests\nnpm test\n\n# bump version\nnpm version minor --no-git-tag # major | minor | patch | prerelease\n\n# nuget pack (only)\nnpm run pack\n\n# nuget publish dev (pack + publish + clean)\nnpm run publish:dev\n```","funding_links":[],"categories":["C\\#","C# #"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsketch7%2FFluentlyHttpClient","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsketch7%2FFluentlyHttpClient","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsketch7%2FFluentlyHttpClient/lists"}