Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
466 changes: 466 additions & 0 deletions Moq.AutoMock.Tests/DescribeHttpClient.cs

Large diffs are not rendered by default.

84 changes: 84 additions & 0 deletions Moq.AutoMock.Tests/DescribeHttpMessageHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
using System.Net;
using System.Net.Http;
using Moq.AutoMock.Http;

namespace Moq.AutoMock.Tests;

[TestClass]
public class DescribeHttpMessageHandler
{
public TestContext TestContext { get; set; }

[TestMethod]
public async Task HttpClient_CanSetupResponses()
{
var mocker = new AutoMocker();

// Setup the mock handler to return a specific response using Protected() API
mocker.GetMock<HttpMessageHandler>()
.SetupHttp(x => x.SendAsync(It.IsAny<HttpRequestMessage>(), It.IsAny<CancellationToken>()))
.ReturnsResponse(HttpStatusCode.OK, "Hello, World!");

var service = mocker.CreateInstance<ServiceWithHttpClient>();

var response = await service.GetAsync("https://example.com/api/test");

Assert.AreEqual(HttpStatusCode.OK, response.StatusCode);
var content = await response.Content.ReadAsStringAsync(TestContext.CancellationToken);
Assert.AreEqual("Hello, World!", content);
}

[TestMethod]
public async Task HttpClient_WithStrickMocks_ThrowsWithoutSetup()
{
var mocker = new AutoMocker(MockBehavior.Strict);

HttpClient httpClient = mocker.GetMock<HttpMessageHandler>().CreateClient();

await Assert.ThrowsAsync<MockException>(() => httpClient.GetAsync("https://example.com/api/test", TestContext.CancellationToken));
}

[TestMethod]
public async Task HttpClient_CanVerifySpecificRequestsWereMade()
{
var mocker = new AutoMocker();

mocker.GetMock<HttpMessageHandler>()
.SetupHttp(x => x.SendAsync(It.IsAny<HttpRequestMessage>(), It.IsAny<CancellationToken>()))
.ReturnsResponse(HttpStatusCode.OK);

var service = mocker.CreateInstance<ServiceWithHttpClient>();

await service.GetAsync("https://example.com/api/users");
await service.PostAsync("https://example.com/api/users", "{}");

mocker.GetMock<HttpMessageHandler>()
.Verify(x => x.SendAsync(
It.Is<HttpRequestMessage>(r => r.Method == HttpMethod.Get),
It.IsAny<CancellationToken>()), Times.Once());

mocker.GetMock<HttpMessageHandler>()
.Verify(x => x.SendAsync(
It.Is<HttpRequestMessage>(r => r.Method == HttpMethod.Post),
It.IsAny<CancellationToken>()), Times.Once());
}

[TestMethod]
public async Task HttpClient_CanSetupSequenceOfResponses()
{
var mocker = new AutoMocker();

mocker.GetMock<HttpMessageHandler>()
.SetupSequence(x => x.SendAsync(It.IsAny<HttpRequestMessage>(), It.IsAny<CancellationToken>()))
.ReturnsResponse(HttpStatusCode.ServiceUnavailable)
.ReturnsResponse(HttpStatusCode.OK, "Success");

var service = mocker.CreateInstance<ServiceWithHttpClient>();

var firstResponse = await service.GetAsync("https://example.com/api/test");
var secondResponse = await service.GetAsync("https://example.com/api/test");

Assert.AreEqual(HttpStatusCode.ServiceUnavailable, firstResponse.StatusCode);
Assert.AreEqual(HttpStatusCode.OK, secondResponse.StatusCode);
}
}
33 changes: 33 additions & 0 deletions Moq.AutoMock.Tests/ServiceWithHttpClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using System.Net.Http;

namespace Moq.AutoMock.Tests;

public class ServiceWithHttpClient(HttpClient httpClient)
{
public HttpClient HttpClient => httpClient;

public Task<HttpResponseMessage> GetAsync(string url)
=> httpClient.GetAsync(url);

public Task<HttpResponseMessage> PostAsync(string url, string content)
=> PostAsync(url, new StringContent(content));

public Task<HttpResponseMessage> PostAsync(string url, HttpContent content)
=> httpClient.PostAsync(url, content);

public Task<HttpResponseMessage> PutAsync(string url, string content)
=> PutAsync(url, new StringContent(content));

public Task<HttpResponseMessage> PutAsync(string url, HttpContent content)
=> httpClient.PutAsync(url, content);

public Task<HttpResponseMessage> DeleteAsync(string url)
=> httpClient.DeleteAsync(url);

public Task<HttpResponseMessage> HeadAsync(string url)
=> httpClient.SendAsync(new HttpRequestMessage()
{
Method = HttpMethod.Head,
RequestUri = new Uri(url)
});
}
2 changes: 1 addition & 1 deletion Moq.AutoMock/AutoMocker.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
using System.ComponentModel.Design;
using System.Diagnostics.CodeAnalysis;
using System.Linq.Expressions;
using System.Reflection;
Expand Down Expand Up @@ -78,6 +77,7 @@ public AutoMocker(MockBehavior mockBehavior, DefaultValue defaultValue, DefaultV
new LazyResolver(),
new FuncResolver(),
new CancellationTokenResolver(),
new HttpClientResolver(),
new MockResolver(mockBehavior, defaultValue, defaultValueProvider, callBase)
];
}
Expand Down
2 changes: 1 addition & 1 deletion Moq.AutoMock/CastChecker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ public static bool DoesReturnPrimitive<TService>(Expression<Func<TService, objec

//expression is assumed to be a lambda so the cast here would cause an exception if it was not the expected format
var lambdaExpression = (LambdaExpression)expression;

//Does its body Convert to System.Object?
if (lambdaExpression.Body.NodeType == ExpressionType.Convert && lambdaExpression.Body.Type == typeof(object))
{
Expand Down
18 changes: 18 additions & 0 deletions Moq.AutoMock/Http/IHttpMessageHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using System.Net.Http;

namespace Moq.AutoMock.Http;

/// <summary>
/// An interface to facilitate mocking the protected <see cref="HttpMessageHandler.SendAsync(HttpRequestMessage, CancellationToken)" /> method.
/// </summary>
public interface IHttpMessageHandler
{
/// <summary>
/// Send an HTTP request as an asynchronous operation.
/// </summary>
/// <param name="request">The HTTP request message to send.</param>
/// <param name="cancellationToken">The cancellation token to cancel operation.</param>
/// <returns>The task object representing the asynchronous operation.</returns>
/// <exception cref="ArgumentNullException">The request was null.</exception>
Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken);
}
Loading
Loading