How can I mock HttpRequestMessage, specifically the CreateResponse?
var requestMessage = Substitute.For<HttpRequestMessage>();
requestMessage.CreateResponse().ReturnsForAnyArgs(
new HttpResponseMessage(HttpStatusCode.OK));
but I get the exception ...
NSubstitute.Exceptions.CouldNotSetReturnDueToNoLastCallException:
'Could not find a call to return from.
I've seen the questions ... How to mock the CreateResponse<T> extension method on HttpRequestMessage
And associated ... ASP.NET WebApi unit testing with Request.CreateResponse ...
But they don't seem to actually end up mocking the CreateResponse
Additional comments:
I'm trying to write a unit test around the starter of an Azure precompiled C# function ...
[FunctionName("Version")]
public static HttpResponseMessage Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)]
HttpRequestMessage req,
TraceWriter log)
{
log.Info("Version function processed a request ... ");
return req.CreateResponse(HttpStatusCode.OK, "Version 0.0.1");
}
and the actual test, where I want to mock up the HttpRequestMessage, specifically the CreateReponse where I get the error is ...
[TestMethod]
public void Version_returns_value()
{
var requestMessage = Substitute.For<HttpRequestMessage>();
requestMessage.CreateResponse(Arg.Any<HttpStatusCode>(), Arg.Any<string>())
.Returns(new HttpResponseMessage(HttpStatusCode.OK));
var log = new CustomTraceWriter(TraceLevel.Verbose);
var httpResponseMessage = VersionFunction.Run(requestMessage, log);
var httpContent = httpResponseMessage.Content;
httpContent.Should().Be("Version 0.0.1 :: valid");
}
No need to mock anything here. Everything can be stubbed safely for this test. CreateResponse is an extension method that, internally, makes use of the request's associated HttpConfiguration. That is the only requirements that needs to be setup before using it in your test.
With that, if you update your test as follows, you should be able to properly exercise your test.
[TestMethod]
public async Task Version_returns_value() {
var expected = "\"Version 0.0.1\"";
var config = new HttpConfiguration();
var requestMessage = new HttpRequestMessage();
requestMessage.SetConfiguration(config);
var log = new CustomTraceWriter(TraceLevel.Verbose);
var httpResponseMessage = VersionFunction.Run(requestMessage, null);
var httpContent = httpResponseMessage.Content;
var content = await httpContent.ReadAsStringAsync();
content.Should().Be(expected);
}
Related
I'm trying to verify the HttpContent of an HttpRequestMessage, but reading the content requires an async operation. How can I do this using Moq?
Illustrative example:
[TestMethod]
public async Task Example()
{
var mockHttpMessageHandler = new Mock<HttpMessageHandler>(MockBehavior.Strict);
mockHttpMessageHandler
.Protected()
.Setup<Task<HttpResponseMessage>>(
"SendAsync",
ItExpr.IsAny<HttpRequestMessage>(),
ItExpr.IsAny<CancellationToken>()
)
.ReturnsAsync(new HttpResponseMessage()
{
StatusCode = HttpStatusCode.OK,
Content = new StringContent(""),
})
.Verifiable();
// call some business component that should send the expected JSON via HTTP Post
await ExecuteSomeBusinessComponentThatPostsViaHttp(httpClient);
this.MockHttpMessageHandler.Protected().Verify(
"SendAsync",
Times.Once(),
ItExpr.Is<HttpRequestMessage>(request =>
JToken.DeepEquals(
// the next line does not compile
JToken.Parse(await request.Content.ReadAsStringAsync()),
JObject.FromObject(new { Result = new { Foo = "Bar" } }))),
ItExpr.IsAny<CancellationToken>());
}
How can I get this test to compile? I.e., how can I use await within an It.Is(...) expression?
Alternatively, is there some other way to test the http request content?
I'm trying to build a dynamic http client using dynamic proxy to intercept the calls and create an http request with it.
The issue i had was with Async methods:
private Task<object> PostAsync(HttpClient client, string url, HttpRequestParameters parameters, Type returnType)
{
return Task.Run(async () =>
{
var requestContent = new StringContent(Serializer.Serialize(parameters.BodyParameters));
var httpResponse = await client.PostAsync(url, requestContent);
var responseContent = await httpResponse.Content.ReadAsStringAsync();
return Serializer.Deserialize(responseContent, returnType);
});
}
My task returns dynamic/object and not the T of the Interception return type.
I thought that i will be able to use it like so
var task = PostAsync(client, url, parameters, returnType);
invocation.ReturnValue = task;
Since the task that will be returned is the original task and it is still pending i thought it would work but all i'm getting is an exception that Task cant be converted to task of my type (Which is string in that case).
Thanks for the helpers
Edit:
I did see Intercept async method, that's what i tried to do but i was unable to call the Task even using reflection, i still got the same exception.
I solved it eventually with a few modifications:
Creating the interceptor with a base object, i used Moq objects to lazy create them and store them in a ConcurrentDictionary for caching.
var mock = new Mock<T>();
var pg = new ProxyGenerator();
return pg.CreateInterfaceProxyWithTarget<T>(GetTarget(clientType), _gatewayInterceptor);
I passed the invocation's return value (in that case Task of T) to a method and got the T.
I wrapped the http call with a new Task of T, await the http call and
return desirialized T result from the task.
Assign the new Task of T back to the return value.
invocation.ReturnValue = GetAsync((dynamic)invocation.ReturnValue, serializer, headers, req);
internal static Task<T> GetAsync<T>(Task<T> originalTask, ISerializer serializer, Headers headers, InvokeHttpRequest req)
{
return Task.Run(async () =>
{
using (HttpClient client = new HttpClient())
{
var httpResponse = await PerformGetAsync(headers, req, client);
var jsonResponse = await httpResponse.Content.ReadAsStringAsync();
return ProcessResult<T>(serializer, jsonResponse);
}
});
}
I know its not the best way to go but it worked for me.
The solution is here if anyone needs it https://github.com/ErezLevip/SimpleProxyClient
I'm trying to use the DefaultHttpContext object to unit test my exception handling middleware.
My test method looks like this:
[Fact]
public async Task Invoke_ProductionNonSuredException_ReturnsProductionRequestError()
{
var logger = new Mock<ILogger<ExceptionHandlerMiddleware>>();
var middleWare = new ExceptionHandlerMiddleware(next: async (innerHttpContext) =>
{
await Task.Run(() =>
{
throw new Exception();
});
}, logger: logger.Object);
var mockEnv = new Mock<IHostingEnvironment>();
mockEnv.Setup(u => u.EnvironmentName).Returns("Production");
var context = new DefaultHttpContext();
await middleWare.Invoke(context, mockEnv.Object);
var reader = new StreamReader(context.Response.Body);
var streamText = reader.ReadToEnd();
//TODO: write assert that checks streamtext is the expected production return type and not the verbose development environment version.
}
In my middleware, I am writing to the context like this:
public static Task WriteResponse(HttpContext context, HttpStatusCode statusCode, object responseData, Formatting jsonFormatting)
{
context.Response.ContentType = "application/json";
context.Response.StatusCode = (int)statusCode;
return context.Response.WriteAsync(JsonConvert.SerializeObject(responseData, jsonFormatting));
}
To give you more insight into the middleware approach I've taken, I'm taking the approach found in this answer here.
Works fine when the app is running it's normal pipeline. However, using the DefaultHttpContext method in the test, the response body always comes back empty, and ContentLength is null. Thus, my streamText variable in the test is an empty string.
Is it possible to inspect what the middleware is writing to the context in this situation? Is this the appropriate way, or is there a better way.
Consider setting the body yourself so that you have control of the stream
Comment provided by #AndrewStanton-Nurse
The Response.Body stream in DefaultHttpContext is Stream.Null, which is a stream that ignores all reads/writes. You need to set the Stream yourself before calling the method.
Further - This will now allow the body to be set, but in order to read it correctly we must set the pointer to the beginning as per this answer, before using the StreamReader.
//...code removed for brevity
var context = new DefaultHttpContext();
context.Response.Body = new MemoryStream();
await middleWare.Invoke(context, mockEnv.Object);
context.Response.Body.Seek(0, SeekOrigin.Begin);
var reader = new StreamReader(context.Response.Body);
var streamText = reader.ReadToEnd();
//...code removed for brevity
We are limiting access to an enterprise system by forcing the client to make their CRUD calls through our application, and then our application will forward that very same request to its destination, saving the header information.
Client makes a request to an ApiController
We pass the request to the service layer
The service layer forwards the request its intended enterprise system destination.
To elaborate on the points above:
The client issues a request against this:
[HttpGet]
[Route("opportunities({id:guid})")]
[Route("opportunities")]
public async Task<HttpResponseMessage> GetOpportunity()
{
var query = Request.RequestUri.AbsolutePath.Split('/').Last() + Request.RequestUri.Query;
var response = await _opportunityService.GetOpportunity(query);
return response;
}
The service method GetOpportunity is defined as:
public async Task<HttpResponseMessage> GetOpportunity(string query)
{//at the line below is where i want to send the same headers that were passed in originally at step 1
var response = Client.Instance.GetAsync(Client.Instance.BaseAddress + query); //this is just using HttpClient to make this call
var responseType = response.Result.StatusCode;
if (responseType == HttpStatusCode.NotFound)
return new HttpResponseMessage
{
StatusCode = responseType
};
return await response;
}
How do we save the header information from Step 1?
By using the following middleware I have been able to grab ALL header information; however, I am not sure on how to cache them or make them available to the service layer:
public class HeaderAuthenticationAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
var headers = actionContext.Request.Headers;
}
}
You are basically acting as a proxy. I see a few possible options.
One would be to pass the Original Request into the service as an explicit dependency
[HttpGet]
[Route("opportunities({id:guid})")]
[Route("opportunities")]
public async Task<HttpResponseMessage> GetOpportunity() {
var response = await _opportunityService.GetOpportunity(this.Request);
return response;
}
and extract the information there
public async Task<HttpResponseMessage> GetOpportunity(HttpRequestMessage Request) {
//at the line below is where i want to send the same headers that were passed in originally at step 1
var query = Request.RequestUri.AbsolutePath.Split('/').Last() + Request.RequestUri.Query;
var headers = Request.Headers;
var url = Client.Instance.BaseAddress + query;
//create new request and copy headers
var proxy = new HttpRequestMessage(HttpMethod.Get, url);
foreach (var header in headers) {
proxy.Headers.Add(header.Key, header.Value);
}
var response = await Client.Instance.SendAsync(proxy);//This is an assumption.
var responseType = response.StatusCode; //Do not mix blocking calls. It can deadlock
if (responseType == HttpStatusCode.NotFound)
return new HttpResponseMessage {
StatusCode = responseType
};
return response;
}
If you do not want to mix the layers and concerns you can extract the needed information into your own model and pass that to the service in order to recreate the needed request.
Currently working with the outlook api, even tough I usually work with the outlook library acquired via Nuget; I have reached a limitation where I am not able to accept event invitations. So I proceeded in making a a restful call out to the the outlook api. However, when I am making the call I am getting the following message {"error":{"code":"InvalidMethod","message":"An action can only be invoked as a 'POST' request."}} when executing the call.
Bad Code
class Program
{
static void Main(string[] args)
{
var testAccept = ExecuteClientCall.AcceptEvent().Result;
}
public static async Task<bool> AcceptEvent()
{
AuthenticationContext authenticationContext = new AuthenticationContext(CrmPrototype.Helpers.AuthHelper.devTenant);
try
{
var token = await GetTokenHelperAsync(authenticationContext, CrmPrototype.Helpers.AuthHelper.OutlookAuthenticationEndpoint);
string requestUrl = "https://outlook.office.com/api/v2.0/Users/***#nowwhere.com/events('AAQkAGZiNDQxZTVkLWQzZjEtNDdjNy04OTc4LTM4NmNjM2JiOTRjNAAQAFpV0CnWR0FIpWFYRtszPHU=')/accept";
HttpClient hc = new HttpClient();
hc.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
var method = new HttpMethod("POST");
var request = new HttpRequestMessage(method, requestUrl)
{
Content = new StringContent("{SendResponse: true}", Encoding.UTF8, "application/json")
};
HttpResponseMessage hrm = await hc.GetAsync(requestUrl);
if (hrm.IsSuccessStatusCode)
{
string jsonresult = await hrm.Content.ReadAsStringAsync();
var stophere = 0;
}
else
{
return false;
}
return true;
}
catch (Exception ex)
{
throw;
}
}
}
Maybe the reason is that you called
hc.GetAsync(requestUrl);
The doc said that this method:
Sends a GET request to the specified Uri as an asynchronous operation.
Try:
PostAsync(Uri, HttpContent)
https://msdn.microsoft.com/en-us/library/system.net.http.httpclient(v=vs.118).aspx
Hope this help you.
Your variable request contains an HttpRequestMessage object that you have created, but your code presently doesn't do anything with it.
Try replacing the line
HttpResponseMessage hrm = await hc.GetAsync(requestUrl);
(which, as pointed out by the other answer, makes a GET request), with
HttpResponseMessage hrm = await hc.SendAsync(request);