refit icon indicating copy to clipboard operation
refit copied to clipboard

feature: Make it easier to add Http Handlers after AddRefitClient<T> has been called

Open oatsoda opened this issue 3 years ago • 7 comments

It would be really useful to have the ability to append more Http Handlers after AddRefitClient<T> has been called, so that in Testing scenarios you can add an interceptor outside of the code that originally called AddRefitClient<T>

For example, I have an Http Mocking package to intercept Http requests for tests. It's pretty simple in most cases to add the Http Handler into existing registered HttpClientFactory registrations by just calling AddHttpClient(clientName) passing in the same name that was used in the original AddHttpClient and it will just append any AddHttpHandler calls on to the existing chain.

But in refit, there's no way to get the name (apart from using reflection, which I currently do) in order to append, as the UniqueName class is not public.

It would be really useful if there was a way to get this name, if simply via making UniqueName public, or else perhaps an extension method to get access to the IHttpClientBuilder for any refit interface T, such as public static IHttpClientBuilder AppendToHttpClient<T>

oatsoda avatar Aug 02 '22 13:08 oatsoda

Possibly relevant: #1330 and #918

oatsoda avatar Aug 02 '22 13:08 oatsoda

In the current version, does the following work for you?:

builder.Services.AddRefitClient<ISomeApi>()
.ConfigureHttpClient(c => c.BaseAddress = new Uri("http://foo"))
.AddHttpMessageHandler(() => new HttpClientDiagnosticsHandler());

Replace "new HttpClientDiagnosticsHandler()" with your mocked handler.

Also, does the following work for you?:

var refitSettings = new RefitSettings
{
    HttpMessageHandlerFactory = () => new HttpClientDiagnosticsHandler(new HttpClientHandler())
};
builder.Services.AddRefitClient<ISomeApi>()
.ConfigureHttpClient(c => c.BaseAddress = new Uri("http://foo"));

Replace "new HttpClientDiagnosticsHandler(new HttpClientHandler())" with your mocked handler.

It seems to me that both approaches work when run as an integration test, not sure if you are hitting an issue getting your mocked handlers to work with the above syntax. If I can understand why this doesn't work for you, then I can reproduce the issue and check if my solution for issue #918 helps.

search4sound avatar Aug 04 '22 04:08 search4sound

@search4sound Imagine that AddRefitClient has been called in Assembly A which I don't have access to the source code. And now in My Assembly B I want to "capture" the IHttpClientBuilder that has been registered in A so that I can append to the chain of handlers.

oatsoda avatar Aug 11 '22 14:08 oatsoda

@search4sound Imagine that AddRefitClient has been called in Assembly A which I don't have access to the source code. And now in My Assembly B I want to "capture" the IHttpClientBuilder that has been registered in A so that I can append to the chain of handlers.

If this is the case then in that scenario Refit is just an implementation detail and your not responsible for testing that assembly as the tests cannot affect or alter the outcomes of assembly A. Assembly A should expose some service that you can mock and use in your tests

dalefrancis88 avatar Aug 28 '22 18:08 dalefrancis88

Well it is an implementation detail, sure, but that means I want my tests to ensure that Refit has been configured correctly with the appropriate startup config, attributes etc.

Attaching a Handler to the registration is the way to do this so that the expected HTTP requests can be checked in the Handler.

When I am testing, my tests will include the production code startup DI registrations so I need a way after the AddRefitClient has already been called to append another handler. I have this working, it's just that it depends on reflection to get the Http Client name which Refit generates for the interface.

I don't really want to be altering my production code to append the additional handler for the tests; besides, I am doing this in a Nuget package I created, so I want to be able to add this facility for anyone to use, without them needing to alter their production code.

oatsoda avatar Jan 19 '23 08:01 oatsoda

@oatsoda

can't you "just" use the IServiceCollection and add an additional registration for your service?

services.Configure<HttpClientFactoryOptions>(NAME,
   options => ...)

This would, however, require us to know the NAME of a generated builder, thus open up UniqueName.ForType<T>() of Refit

kirides avatar May 22 '23 11:05 kirides

@kirides Yeah, knowing the name is basically the issue (details in my original post) - so opening up UniqueName.ForType<T> would mean I can avoid the reflection.

Here is a test demonstrating how you use my package to test your production code - where the production code registers a Refit interface and your test calls that setup production code.

So the test wants to just "append" an Http Handler on to the Http Client registered. https://github.com/oatsoda/TeePee/blob/main/TeePee.Refit.Tests/ServiceCollectionExtensionsTests.cs

oatsoda avatar Jul 27 '23 15:07 oatsoda