dotnet-sdk icon indicating copy to clipboard operation
dotnet-sdk copied to clipboard

Injecting or passing in HttpClient

Open johanan opened this issue 4 years ago • 4 comments

It would help testing if I could pass in HttpClient or IHttpClientFactory into DaprClientBuilder.

I can see an internal HttpClient Factory used for internal testing:

https://github.com/dapr/dotnet-sdk/blob/master/src/Dapr.Client/DaprClientBuilder.cs#L70

internal DaprClientBuilder UseHttpClientFactory(Func<HttpClient> factory)
  {
      this.HttpClientFactory = factory;
      return this;
  }

Which then is used here: https://github.com/dapr/dotnet-sdk/blob/master/src/Dapr.Client/DaprClientBuilder.cs#L158

And that there is an extension in the ASP.NET Core project: https://github.com/dapr/dotnet-sdk/blob/master/src/Dapr.AspNetCore/DaprServiceCollectionExtensions.cs#L39

            services.AddSingleton(_ =>
            {
                var builder = new DaprClientBuilder();
                if (configure != null)
                {
                    configure.Invoke(builder);
                }

                return builder.Build();
            });

Can there be constructor that takes in an HttpClient? Or an IHttpClientFactory?

With that I can wire up my test cases to use test handlers to ensure the responses I want out.

I know that I can wrap Dapr in an interface and inject that.

johanan avatar Sep 27 '21 17:09 johanan

I can see the benefit of extracting an interface for the DaprClientBuilder or DaprClient so it can be injected and mocked in the classes of the application we are building. However, I dont' see why we we would need to inject an interface (hence mock) in the DaprClientBuilder itself.

Can you provide more context on how you are using this?

fbridger avatar Oct 02 '21 13:10 fbridger

I think having an interface for DaprClient will fulfill the need for what I am looking for. An IDaprClient would be perfect for registering, injecting, and mocking.

I agree that HttpClient wouldn't need to be injected after thinking about the context a little more.

johanan avatar Oct 04 '21 18:10 johanan

Commenting here for clarity what I said on PR #769

This is not a PR that we can accept, because it makes it impossible for us to add functionality to these types as functionality is added to Dapr. It's very intentional that these are classes. Let's try to solve #756 in another way.

@johanan - what's the real need driving this? Do you have an example of the code you want to write for testing?

I am asking because testing your own use of the DaprClient via HttpClient is a very low level approach. If you decide to test DaprClient via HttpClient you will be subject to any and all changes we make to the Dapr API protocol as it evolves. How we communicate with the Dapr sidecar is an implementation detail for the SDK and we don't make any guarantees about compatibility.

rynowak avatar Oct 18 '21 21:10 rynowak

@rynowak I agree with you. I don't want an IHttpClient. I initially was thinking that I could mock out the calls and responses, but your points show the problems with that approach.

I think having the Dapr Client have an interface would work. Then that could be mocked out and injected.

Right now I am just wrapping the actions I need in an interface and then having Dapr fulfill those. Here is an example that is modified to hide specifics to demonstrate:

public interface IPermissions
    {
        Task<IEnumerable<Permissions>> GetPermissions();
    }

Then a class to implement:

public class PermissionRepository : IPermissions
    {
        public async Task<IEnumerable<Permissions>> GetPermissions()
        {
            var dapr = new DaprClientBuilder().Build();
            var req = dapr.CreateInvokeMethodRequest(HttpMethod.Get, "user-microservice", "api/users/getpermissions");

            return await dapr.InvokeMethodAsync<Permissions[]>(req);
        }
    }

This works and we just create a test implementation and inject that.

The use-case I was specifically trying to account for was a pre-integrated test step. We fire up an instance of a SQL server using docker that we fully test the data stack. We don't mock up the data call. It allows us to trust that we have the correct SQL written, the correct methods for EF core, etc.

I guess my thoughts were along that path. I want to test to make sure that we call the correct microservice, using the correct path, and the correct parameters. Stubbing out a test implementation only tests that everything else is working as it should. I want to test that we call Dapr correctly. That is what initially made me think about the HttpClient interface.

I want to test pieces of a microservice as close to production before it gets deployed to an integration or end-to-end test.

johanan avatar Oct 19 '21 15:10 johanan