I am testing some .net Core middleware and would like to run the middleware with the whole asp.net Core http pipeline instead of mocking it.
The problem is that somehow the Response object is not being set in the httpRequest when I use the Feature Collection and it is read only on the Request itself.
This code throws an exception when it tries to write to the Response Stream.
var fc = new FeatureCollection();
fc.Set<IHttpRequestFeature>(new HttpRequestFeature {
Headers = new HeaderDictionary { { "RandomHeaderName", "123" } }
});
var httpContext = new DefaultHttpContext(fc);
var middleware = new RequestValidationMiddleware(
next: async (innerHttpContext) =>
{
await innerHttpContext.Response.WriteAsync("test writing");
});
middleware.InvokeAsync(httpContext).GetAwaiter().GetResult();
By using a custom feature collection, you are excluding features that would have been added by the default constructor of the DefaultHttpContext
public DefaultHttpContext()
: this(new FeatureCollection())
{
Features.Set<IHttpRequestFeature>(new HttpRequestFeature());
Features.Set<IHttpResponseFeature>(new HttpResponseFeature());
Features.Set<IHttpResponseBodyFeature>(new StreamResponseBodyFeature(Stream.Null));
}
public DefaultHttpContext(IFeatureCollection features)
{
_features.Initalize(features);
_request = new DefaultHttpRequest(this);
_response = new DefaultHttpResponse(this);
}
try recreating what was done in the default constructor by also adding the required features needed to exercise your test
var fc = new FeatureCollection();
fc.Set<IHttpRequestFeature>(new HttpRequestFeature {
Headers = new HeaderDictionary { { "RandomHeaderName", "123" } }
});
//Add response features
fc.Set<IHttpResponseFeature>(new HttpResponseFeature());
var responseBodyStream = new MemoryStream();
fc.Set<IHttpResponseBodyFeature>(new StreamResponseBodyFeature(responseBodyStream ));
var httpContext = new DefaultHttpContext(fc);
Related
I've been getting non-deterministic test results and decided to delve deeper into the issue. I've ended up on two tests behaving differently based on whether I Debug them, or just use Run. I've managed to magically fix one by deleting and renaming it.
I'm testing my API endpoint for updating usernames via WebApplicationFactory(I'm using Mediator and a RavenDb that I recreate between every test if that makes a difference). The problem test case is User_Can_Not_Update_Username_If_It_Is_Already_Taken(). I'm expecting a Conflict response, but most of the time I'm getting OK. The weirdest thing is that the response varies on where I put my breakpoint:
OK
Conflict.
I've tried clearing Rider cache and looking into my FakeApp not disposing correctly. Trying to Debug before the UpdateUsernameCommandHandlerresults in the Conflict response status code, so I'm really at loss right now.
Here is the code of the test case:
[Fact]
public async Task User_Can_Not_Update_Username_If_It_Is_Already_Taken()
{
// Arrange
using var app = new FakeApp(DatabaseFixture.TestDbName);
var registerUserCommand = new RegisterUserCommand
{
Email = "oleksandr.torianyk#gmail.com"
};
var registerUserStringPayload = JsonConvert.SerializeObject(registerUserCommand);
var registerUserHttpContent = new StringContent(registerUserStringPayload, Encoding.UTF8, "application/json");
var registerUserResponse = await app.Client.PostAsync("/user", registerUserHttpContent);
var initialUpdateUsernameCommand = new UpdateUsernameCommand
{
Id = new Guid(await registerUserResponse.Content.ReadAsStringAsync()),
Username = "All-ToR"
};
var initialUpdateUsernameStringPayload = JsonConvert.SerializeObject(initialUpdateUsernameCommand);
var initialUpdateUsernameHttpContent = new StringContent(initialUpdateUsernameStringPayload, Encoding.UTF8, "application/json");
await app.Client.PutAsync("/user/username", initialUpdateUsernameHttpContent);
var updateUsernameCommand = new UpdateUsernameCommand
{
Id = new Guid(await registerUserResponse.Content.ReadAsStringAsync()),
Username = "All-ToR"
};
var updateUsernameStringPayload = JsonConvert.SerializeObject(updateUsernameCommand);
var updateUsernameHttpContent = new StringContent(updateUsernameStringPayload, Encoding.UTF8, "application/json");
// Act
var response = await app.Client.PutAsync("/user/username", updateUsernameHttpContent);
// Assert
response.StatusCode.Should().Be(HttpStatusCode.Conflict);
}
FakeApp
public class FakeApp : IDisposable
{
private readonly WebApplicationFactory<Startup> _appFactory;
public HttpClient Client { get; }
public FakeApp(string ravenDbName = default)
{
_appFactory = new WebApplicationFactory<Startup>().WithWebHostBuilder(webHostBuilder =>
webHostBuilder.ConfigureServices(services =>
{
var configuration = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.Development.json", true)
.AddEnvironmentVariables()
.Build();
services.AddRavenDb(configuration, ravenDbName);
services.AddDependencies();
services.AddMediatR(Assembly.GetExecutingAssembly());
}));
Client = _appFactory.CreateClient();
}
public void Dispose()
{
Client.Dispose();
_appFactory.Dispose();
}
}
I've written a test using Test Server in dot net core 3.1 and I'm trying to do a PATCH request to an endpoint. However as I'm new to using PATCH, I'm a bit stuck with how to send the correct object that the endpoint is expecting.
[Fact]
public async Task Patch()
{
var operations = new List<Operation>
{
new Operation("replace", "entryId", "'attendance ui", 5)
};
var jsonPatchDocument = new JsonPatchDocument(operations, new DefaultContractResolver());
// Act
var content = new StringContent(JsonConvert.SerializeObject(jsonPatchDocument), Encoding.UTF8, "application/json");
var httpResponse = await HttpClient.PatchAsync($"v1/Entry/1", content);
var actual = await httpResponse.Content.ReadAsStringAsync();
}
[HttpPatch("{entryId}")]
public async Task<ActionResult> Patch(int entryId, [FromBody] JsonPatchDocument<EntryModel> patchDocument)
{
if (patchDocument == null)
{
return BadRequest();
}
var existingEntry = _mapper.Map<EntryModel>(await _entryService.Get(entryId));
patchDocument.ApplyTo(existingEntry);
var entry = _mapper.Map<Entry>(existingEntry);
var updatedEntry = _mapper.Map<Entry>(await _entryService.Update(entryId, entry));
return Ok(await updatedEntry.ModelToPayload());
}
From the example I'm creating a JsonPatchDocument with a list of operations, serializing it to JSON and then doing PatchAsync with HTTP Client with the URL for the endpoint.
So my question is what is the shape of the object that I should be Patching and I'm doing this correctly in general?
I tried sending the EntryModel as shown in the picture below, however patchDocument.Operations has an empty list.
Thanks,
Nick
I ended up solving my problem by doing several things:
JsonPatchDocument doesn't seem to work without the dependency
services.AddControllers().AddNewtonsoftJson(); in Startup.cs. This is from the Nuget package `Microsoft.AspNetCore.Mvc.Newtonsoft.json.
There is an easier way to create the array than the answer from #Neil. Which is this: var patchDoc = new JsonPatchDocument<EntryModel>().Replace(o => o.EntryTypeId, 5);
You need this specific media type:
var content = new StringContent(JsonConvert.SerializeObject(patchDoc), Encoding.UTF8, "application/json-patch+json");
Here is the complete code:
/// <summary>
/// Verify PUT /Entrys is working and returns updated records
/// </summary>
[Fact]
public async Task Patch()
{
var patchDoc = new JsonPatchDocument<EntryModel>()
.Replace(o => o.EntryTypeId, 5);
var content = new StringContent(JsonConvert.SerializeObject(patchDoc), Encoding.UTF8, "application/json-patch+json");
var httpResponse = await HttpClient.PatchAsync($"v1/Entry/1", content);
var actual = await httpResponse.Content.ReadAsStringAsync();
// Assert
Assert.Equal(HttpStatusCode.OK, httpResponse.StatusCode);
Assert.True(httpResponse.IsSuccessStatusCode);
}
/// <summary>
/// Endpoint to do partial update
/// </summary>
/// <returns></returns>
[HttpPatch("{entryId}")]
public async Task<ActionResult> Patch(int entryId, [FromBody] JsonPatchDocument<EntryModel> patchDocument)
{
if (patchDocument == null)
{
return BadRequest();
}
var existingEntry = _mapper.Map<EntryModel>(await _entryService.Get(entryId));
// Apply changes
patchDocument.ApplyTo(existingEntry);
var entry = _mapper.Map<Entry>(existingEntry);
var updatedEntry = _mapper.Map<Entry>(await _entryService.Update(entryId, entry));
return Ok();
}
Take a look at this page: https://learn.microsoft.com/en-us/aspnet/core/web-api/jsonpatch?view=aspnetcore-3.1
But the content is something like:
[ {
"op": "add",
"path": "/customerName",
"value": "Barry" }, {
"op": "add",
"path": "/orders/-",
"value": {
"orderName": "Order2",
"orderType": null
} } ]
The JsonPatchDocument will not work. To make it work you have to add a media type formater.
Install Microsoft.AspNetCore.Mvc.NewtonsoftJson
In startup.cs , after AddControllers() add ->
.AddNewtonsoftJson(x =>
x.SerializerSettings.ContractResolver =
new CamelCasePropertyNamesContractResolver(); })
If you need to have JSON as the default media type formatter. Keep it before any other media type of formatter.
I have a method that deletes multiple events. Currently the code is as following:
public async Task DeleteEvents(IEnumerable<string> eventExternalIds)
{
foreach(var eventExternalId in eventExternalIds)
{
await DeleteEvent(eventExternalId);
}
}
public async Task DeleteEvent(string eventExternalId)
{
await GraphClient
.Users[Username]
.Calendars[CalendarName]
.Events[eventExternalId]
.Request()
.DeleteAsync();
}
I would imagine it won't perform well with any significant number of id's to delete. Is there a way to delete them all in a batch(es) instead of each individually?
msgraph-sdk-dotnet v1.15.0 or above
For msgraph-sdk-dotnet version 1.15.0 or above the support for Batch request has been introduced via BatchRequestContent class
Example
//1. construct a Batch request
var batchRequestContent = new BatchRequestContent();
var step = 1;
foreach (var eventId in eventIds)
{
var requestUrl = graphClient
.Me
.Events[eventId]
.Request().RequestUrl;
var request = new HttpRequestMessage(HttpMethod.Delete, requestUrl);
var requestStep = new BatchRequestStep(step.ToString(), request, null);
batchRequestContent.AddBatchRequestStep(requestStep);
step++;
}
//2. Submit request
var batchRequest = new HttpRequestMessage(HttpMethod.Post, "https://graph.microsoft.com/v1.0/$batch");
batchRequest.Content = batchRequestContent;
await graphClient.AuthenticationProvider.AuthenticateRequestAsync(batchRequest);
var httpClient = new HttpClient();
var batchResponse = await httpClient.SendAsync(batchRequest);
//3. Process response
var batchResponseContent = new BatchResponseContent(batchResponse);
var responses = await batchResponseContent.GetResponsesAsync();
foreach (var response in responses)
{
if (response.Value.IsSuccessStatusCode)
{
//...
}
}
Issues
while targeting NetCore 2.1 or above or .NET Framework
NullReferenceException exception might occur, to address this issue
you could switch to 1.16.0-preview.1 (details)
Limitations
Note: A batch cannot hold more that 20 requests
msgraph-sdk-dotnet v1.14.0 or older
For previous versions, the following example demonstrates how to implement a support for Batch request:
var batchRequest = new BatchRequest();
foreach (var eventId in eventIds)
{
var request = graphClient.Me.Events[eventId].Request();
batchRequest.AddQuery(request,HttpMethod.Delete);
}
var responses = await graphClient.SendBatchAsync(batchRequest);
where BatchRequest is a custom class which adds support for JSON Batching
I'm trying to find a way to write an integration test for a component which that is dependent on HttpContext and uses cookies.
My problem is an Exception is thrown when it tries to write anything to the response cookies.
Here's some code to reproduce the problem.
using System.IO;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Web;
using Moq;
namespace CookieTest
{
[TestClass]
public class CookieTest
{
private Mock<HttpContextBase> _httpContextMock;
[TestInitialize]
public void SetUp()
{
var request = new HttpRequest(null, "http://localhost/", "");
var stream = new MemoryStream();
var sw = new StreamWriter(stream);
var response = new HttpResponse(sw);
_httpContextMock = new Mock<HttpContextBase>();
_httpContextMock.Setup(t => t.Request).Returns(new HttpRequestWrapper(request));
_httpContextMock.Setup(t => t.Response).Returns(new HttpResponseWrapper(response));
}
[TestMethod]
public void TestCookieWrite()
{
var httpContext = _httpContextMock.Object;
var expectedValue = "value";
var cookies = httpContext.Response.Cookies;
var cookieToAdd = new HttpCookie("key", expectedValue);
// to illustrate that these are not null
Assert.IsNotNull(cookies);
Assert.IsNotNull(cookieToAdd);
// System.NullReferenceException: Object reference not set to an instance of an object.
// at System.Web.HttpCookieCollection.Add(HttpCookie cookie)
// at CookieTest.CookieTest.TestCookieWrite() in CookieTest.cs: line 41
cookies.Add(cookieToAdd);
Assert.AreEqual(expectedValue, httpContext.Response.Cookies.Get("key"));
}
}
}
The response contains a private field called _context which requires a reference to both itself and the request, if you set the field like so:
var response = new HttpResponse(sw);
response.GetType()
.GetField("_context", BindingFlags.NonPublic | BindingFlags.Instance)
.SetValue(response, new HttpContext(request, response));
_httpContextMock = new Mock<HttpContextBase>();
Then the NullReferenceException will no longer be thrown. Additionally, the test was failing as you compare the HttpCookie object with a string, you need to edit the assertion to check against the Value property like so:
Assert.AreEqual(expectedValue, httpContext.Response.Cookies.Get("key").Value);
I am working on a Windows app and am having some issues with cookies. Please note that I am working with Windows.Web.Http, not the System namespace HttpClient.
The API I'm working with uses an auth-header for authentication. Basically after a POST to login, I need a way to get the cookies returned and then use those cookies to perform the subsequent API calls. I posted an example of what I currently have, which succeeds. I can see the cookies in the result object. I'm just not entirely sure where to go from here / how to proceed. Thanks! Any ideas?
using MyApi.Interfaces;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Windows.Web.Http;
using Newtonsoft.Json;
using MyApi.Models.Auth;
using MyApi.Models;
namespace MyApi
{
public class MyService
{
private const string MyBaseUrl = "http://api.my.com:3000";
private readonly HttpClient _httpClient = new HttpClient();
public async Task<SignInResponse> AttemptLogin(string username, string password)
{
if (string.IsNullOrEmpty(username) || string.IsNullOrEmpty(password))
throw new ArgumentException("Username or password is null or empty");
var uri = new Uri(string.Format("{0}/{1}", MyBaseUrl, "auth/signin"));
var authSignIn = new Models.Auth.SignInRequest();
authSignIn.Email = username;
authSignIn.Password = password;
var myObject = JsonConvert.SerializeObject(authSignIn);
// I see the headers in the result object, but I'm not
// sure the best way to a) get them out and b) shove them into
// all of the next calls
var result = await _httpClient.PostAsync(uri,
new HttpStringContent(myObject.ToString(),
Windows.Storage.Streams.UnicodeEncoding.Utf8,
"application/json"));
var content = await result.Content.ReadAsStringAsync();
var successResponse = new SignInResponse();
try
{
successResponse = JsonConvert.DeserializeObject<SignInResponse>(content);
}
catch (Exception)
{
var failResponse = JsonConvert.DeserializeObject<ErrorResponse>(content);
throw new Exception(failResponse.message);
}
return successResponse;
}
}
}
You can use HttpBaseProtocolFilter.CookieManager, e.g.:
var filter = new HttpBaseProtocolFilter();
var cookieManager = filter.CookieManager;
var uri = new Uri("http://api.my.com:3000");
foreach (var cookie in cookieManager.GetCookies(uri))
{
Debug.WriteLine(cookie.Name);
Debug.WriteLine(cookie.Value);
}
Notice, if the cookies are already in the HttpCookieContainer, the cookies will be automatically added in the next requests to http://api.my.com:3000, and no action is required from your side.
If you want to modify them or delete them, the HttpCookieContainer has methods to do that.
Take a look at Flurl. It presents a fluent interface over the Http bits, so you can say something like this to authenticate and reuse the connection with the cookies:
using (var fc = new FlurlClient().EnableCookies())
{
var url = new Url( "http://api.com/endpoint" ) ;
await url
.AppendPathSegment("login")
.WithClient(fc)
.PostUrlEncodedAsync(new { user = "user", pass = "pass" });
var page = await url
.AppendPathSegment("home")
.WithClient(fc)
.GetStringAsync();
// Need to inspect the cookies? FlurlClient exposes them as a dictionary.
var sessionId = fc.Cookies["session_id"].Value;
}