Reputation: 395
Update 20221024: I have used Ruikai Feng's solution in order to use Mockoon with my tests. I realize this is not a correct approach from a unit testing approach and am working to change my approach.
Update 20221019: I have been using moq to mock out the IHttpClientFactory. The reason why I wanted to instantiate it was to call mock apis created in a tool called Mockoon which replicates apis. I have been so far unable to call these APIs likely because I have not yet properly mocked the ihttpclientfactory. I appreciate all the feedback as the solution is still ongoing at this time.
I am using a .NET 6 Web API controller with IHttpClientFactory to perform external API calls. As such, I have the following constructor:
public MyController(IHttpClientFactory httpClientFactory)
{
_httpClientFactory = httpClientFactory;
}
This works because in my Program.cs
I add an HTTP Client to my builder.Services
.
In my tests, how do I instantiate/set up the httpClientFactory
for the controller because I need it to instantiate my controller: var controller = new MyController(httpClientFactory);
generates an error since there isn't any settings added.
I ran into a similar issue with configurations from appsettings.json and resolved with ConfigurationBuilder
but there doesn't seem to be a similar one for IHttpClientFactory
.
If you need any more information, please let me know. Thanks!
Upvotes: 2
Views: 1998
Reputation: 11
I'd like to amend @Peter CSala's answer some, since it got me most of the way there but not entirely. My service class uses the IHttpClientFactory
but without any named client. When setting up the above code and running as is, I'm met with an exception:
Message:
System.NotSupportedException : Unsupported expression: factory => factory.CreateClient()
Extension methods (here: HttpClientFactoryExtensions.CreateClient) may not be used in setup / verification expressions.
Stack Trace:
Guard.IsOverridable(MethodInfo method, Expression expression) line 87
ExpressionExtensions.<Split>g__Split|5_0(Expression e, Expression& r, MethodExpectation& p, Boolean assignment, Boolean allowNonOverridableLastProperty) line 234
ExpressionExtensions.Split(LambdaExpression expression, Boolean allowNonOverridableLastProperty) line 149
Mock.SetupRecursive[TSetup](Mock mock, LambdaExpression expression, Func`4 setupLast, Boolean allowNonOverridableLastProperty) line 643
Mock.Setup(Mock mock, LambdaExpression expression, Condition condition) line 498
Mock`1.Setup[TResult](Expression`1 expression) line 452
MyServiceTests.GetHttpClientFactoryMock(Int64 contentLength) line 84
MyServiceTests.DoesCopy_PutObject() line 55
GenericAdapter`1.GetResult()
AsyncToSyncAdapter.Await(Func`1 invoke)
TestMethodCommand.RunTestMethod(TestExecutionContext context)
TestMethodCommand.Execute(TestExecutionContext context)
<>c__DisplayClass1_0.<Execute>b__0()
DelegatingTestCommand.RunTestMethodInThreadAbortSafeZone(TestExecutionContext context, Action action)
This is because the parameter-less call to IHttpClientFactory.CreateClient()
is actually an extension method, where the method call for a named client is not. Looking into the extension method, it actually is just a convenience wrapper for the named client method using a default name from Microsoft.Extensions.Options
.
All you have to do is actually mock the named client method to return the mocked HttpClient
from Peter's example above when requesting the default name. Here is the entire code I used to mock the IHttpClientFactory
to work with a default client and workaround the above exception:
using Microsoft.Extensions.Options;
var mockHandler = new Mock<DelegatingHandler>();
mockHandler.Protected()
.Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>())
.ReturnsAsync(new HttpResponseMessage(HttpStatusCode.OK)
.Verifiable();
mockHandler.As<IDisposable>().Setup(s => s.Dispose());
var httpClient = new HttpClient(mockHandler.Object);
var mockFactory = new Mock<IHttpClientFactory>(MockBehavior.Strict);
mockFactory
.Setup(factory => factory.CreateClient(Options.DefaultName))
.Returns(httpClient)
.Verifiable();
// Use the factory from above in your tests
// Calls in the service to IHttpClientFactory.CreateClient() will
// return the HttpClient using the mocked handler from above :)
var res = myService.DoTheThing(mockFactory.Object);
Upvotes: 1
Reputation: 22679
In order to be able to use a properly mocked IHttpClientFactory
in your unit test you need to do the following steps:
DelegatingHandler
mockvar mockHandler = new Mock<DelegatingHandler>();
mockHandler.Protected()
.Setup<Task<HttpResponseMessage>>("SendAsync", It.IsAny<HttpRequestMessage>(), It.IsAny<CancellationToken>())
.ReturnsAsync(new HttpResponseMessage(HttpStatusCode.OK))
.Verifiable();
mockHandler.As<IDisposable>().Setup(s => s.Dispose());
This sample mock will always return with 200 OK status code and without a response body
HttpClient
var httpClient = new HttpClient(mockHandler.Object);
It creates an HttpClient
instance and pass the above handler to it
IHttpClientFactory
mockvar mockFactory = new Mock<IHttpClientFactory>(MockBehavior.Strict);
mockFactory
.Setup(factory => factory.CreateClient())
.Returns(httpClient)
.Verifiable();
It setups an IHttpClientFactory
mock to return the above HttpClient
for the CreateClient
method call
IHttpClientFactory
to create a named client then change the Setup
to this .Setup(factory => factory.CreateClient(It.IsAny<string>()))
mockFactory.Verify(factory => factory.CreateClient(), Times.Once);
mockHandler.Protected()
.Verify("SendAsync", Times.Once(), It.IsAny<HttpRequestMessage>(), It.IsAny<CancellationToken>());
Upvotes: 2
Reputation: 11546
I tried as below:
[TestFixture]
public class IndexActionTests
{
private HomeController controller;
[SetUp]
public void Setup()
{
var services = new ServiceCollection();
services.AddHttpClient();
var provider = services.BuildServiceProvider();
var httpclientfactory = provider.GetService<IHttpClientFactory>();
controller = new HomeController(httpclientfactory);
}
[Test]
public void Test1()
{
var result = controller.Index();
Assert.AreEqual(typeof(ViewResult),result.GetType());
}
}
Result:
Upvotes: 0