Testing Custom Factory Middleware - c#

I am creating an asp.net core web app, which creates routes from user defined sources (config, database etc). I have used a middleware factory based approach which works fine, except I do not know how to isolate a middleware for unit/integration testing. I use the route data from the HttpContext to validate the incoming request against the user defined configuration source, so I need the HttpContext to have RouteData, which is why I am leaning towards Integration testing with xUnit.
If I make a request using a httpclient created via AspNetCore.TestHost.TestServer.CreateCLient(), I work my way through my entire call chain, which is what I expect. What I need to be able to do is terminate the middleware or provide the HttpContext with RouteData.
I have tried an ugly unit test using AutoFixture and the DefaultHttpContext(), but as expected, I don't get any RouteData on my HttpContext, so my tests can never pass.
Middleware
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
try
{
if (_flowApis.Count == 0)
throw new ArgumentNullException();
var doesMatch = false;
var routeData = context.GetRouteData();
foreach (var api in _flowApis)
{
if (!RequestType.IsValidRequestType(api.Request.Type))
throw new InvalidOperationException();
else
foreach (Route route in routeData.Routers.Where(r => r.GetType() == typeof(Route)))
{
if (route.RouteTemplate == api.Route)
{
if (route.Constraints.TryGetValue("httpMethod", out IRouteConstraint routeConstraint))
{
var value = (HttpMethodRouteConstraint)routeConstraint;
if (value.AllowedMethods.Contains(api.Method))
{
doesMatch = true;
var routeValues = GetRouteValues(routeData);
var request = new Core.Request(context.Request, api, routeValues);
context.Items.Add(nameof(Core.Request), request);
break;
}
}
}
}
if (doesMatch)
break;
}
if (!doesMatch)
{
context.Response.StatusCode = 404;
await context.Response.WriteAsync("", new CancellationToken());
}
else
await next(context);
}
catch(ArgumentNullException ex)
{
var mex = new MiddleWareException<MatchRouteToFlowApiMiddleware>("Flow Configuration in app.settings has not been set.", ex);
context.Response.StatusCode = 500;
await context.Response.WriteAsync("", new CancellationToken());
}
catch (InvalidOperationException ex)
{
var mex = new MiddleWareException<MatchRouteToFlowApiMiddleware>("Invalid Request Type", ex);
context.Response.StatusCode = 500;
await context.Response.WriteAsync("", new CancellationToken());
}
}
Unit Test
[TestMethod]
public async Task Request_path_matches_an_api_route_and_method()
{
var fixture = ScaffoldFixture();
var flowApi = fixture.Build<FlowApi>()
.With(p => p.Route, "/some/path")
.With(m => m.Method, "GET")
.Create();
var flowApiRequest = fixture.Build<Request>()
.With(t => t.Type, "CData")
.Create();
flowApi.Request = flowApiRequest;
var flowConfiguration = fixture.Create<FlowConfiguration>();
flowConfiguration.FlowApis.Clear();
flowConfiguration.FlowApis.Add(flowApi);
var middleware = new MatchRouteToFlowApiMiddleware(flowConfiguration: flowConfiguration);
var context = new DefaultHttpContext();
context.Request.Method = "GET";
context.Request.Path = "/some/path";
context.Response.Body = new MemoryStream();
await middleware.InvokeAsync(context, (httpContext) => Task.FromResult(0));
context.Items.ContainsKey(nameof(Core.Request)).Should().BeTrue();
}
Integration test
[Fact]
public async Task Request_path_matches_an_api_route_and_method()
{
//Arrange
var response = await _httpClient.GetAsync("/some/route");
response.StatusCode.Should().Be(500);
}
When executing the unit test, I can never satisfy the inner for each beacuse I don't have route data on the HttpContext.
When executing the integration test, the middleware executes as expected but since it does not terminate, i cannot validate

I realised that trying to terminate my middleware just to validate it using xUnit meant I would not be testing my system as is, so I looked more at my unit test setup.
I have managed to get Route Data on the HttpContext. It feels messy but is a starting point at least.
private static DefaultHttpContext DefaultHttpContextWithRoute(Fixture fixture)
{
var context = new DefaultHttpContext();
var routeDictionary = new RouteValueDictionary
{
{ "some","path" }
};
context.Features.Set<IRoutingFeature>(new RoutingFeature());
context.Features.Get<IRoutingFeature>().RouteData = new RouteData(routeDictionary);
var inline = fixture.Create<DefaultInlineConstraintResolver>();
var route = new Route(new TestRouter(), "/some/path", inline);
var httpMethodRouteConstraint = new HttpMethodRouteConstraint("GET");
route.Constraints.Add("httpMethod", httpMethodRouteConstraint);
context.Features.Get<IRoutingFeature>().RouteData.Routers.Add(route);
context.Request.Method = "GET";
context.Request.Path = "/some/path";
context.Response.Body = new MemoryStream();
return context;
}
private class TestRouter : IRouter
{
public VirtualPathData GetVirtualPath(VirtualPathContext context)
{
throw new NotImplementedException();
}
public Task RouteAsync(RouteContext context)
{
throw new NotImplementedException();
}
}
In each unit test I call the DefaultHttpContext and use invoke my middleware like so
DefaultHttpContext context = DefaultHttpContextWithRoute(fixture);
await middleware.InvokeAsync(context, (httpContext) => Task.FromResult(0));

Related

Mocked HttpClientFactory returns null when creating client

I am trying to unit test a service that uses the IHttpClientFactory with Nunit and NSubstitute for mocking.
The service I want to test looks like this
public class Movies : IMovies
{
private readonly IHttpClientFactory _httpClientFactory;
public Movies(IHttpClientFactory httpClientFactory)
{
_httpClientFactory = httpClientFactory;
}
public async Task<MovieCollection> GetPopularMovies(int PageNumber = 1)
{
// Get an instance of HttpClient from the factpry that we registered
// in Startup.cs
var client = _httpClientFactory.CreateClient("Movie Api");
// Call the API & wait for response.
// If the API call fails, call it again according to the re-try policy
// specified in Startup.cs
var result =
await client.GetAsync($"movie/popular?api_key=<the_api_key>language=en-US&page={PageNumber}");
if (result.IsSuccessStatusCode)
{
// Read all of the response and deserialise it into an instace of
var content = await result.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<MovieCollection>(content);
}
return null;
}
}
When I run the test I get an error that says
System.NullReferenceException : Object reference not set to an instance of an object.
at MovieApi.Services.Movies.GetPopularMovies(Int...
Here is the test I am running.The error occurs only when I put the keyword await in the line
var result = await service.GetPopularMovies(1);
Check the Test code below:
[Test]
public async Task GetPopular_WhenCalled_ReturnOK()
{
//arrange
var moviecollection = new MovieCollection();
var httpClientFactoryMock = Substitute.For<IHttpClientFactory>();
var fakeHttpMessageHandler = new FakeHttpMessageHandler(new HttpResponseMessage() {
StatusCode = HttpStatusCode.OK,
Content = new StringContent(JsonConvert.SerializeObject(moviecollection), Encoding.UTF8, "application/json")
});
var fakeHttpClient = new HttpClient(fakeHttpMessageHandler);
httpClientFactoryMock.CreateClient().Returns(fakeHttpClient);
// Act
var service = new Movies(httpClientFactoryMock);
var result = await service.GetPopularMovies(1);
//assert
Assert.IsNotNull(result);
}
The subject method under test calls
var client = _httpClientFactory.CreateClient("Movie Api");
but you configure the mock to return when CreateClient() is invoked.
httpClientFactoryMock.CreateClient().Returns(fakeHttpClient);
which means that when testing and CreateClient("Movie Api") is invoked the mock wont know what to do and thus returns null, causing the next call to throw NRE
Setup the mock to behave as expected when the system under test is invoked.
//...
httpClientFactoryMock.CreateClient("Movie Api").Returns(fakeHttpClient);
//...

Unit testing an AuthorizeAttribute on an ASP.NET Core MVC API controller

I have a ASP.NET Core MVC API with controllers that need to be unit tested.
Controller:
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using System.Threading.Tasks;
namespace TransitApi.Api.Controllers
{
[Route("api/foo")]
public class FooController : Controller
{
private IFooRepository FooRepository { get; }
public FooController(IFooRepository fooRepository)
{
FooRepository = fooRepository;
}
[HttpGet]
[Authorize("scopes:getfoos")]
public async Task<IActionResult> GetAsync()
{
var foos = await FooRepository.GetAsync();
return Json(foos);
}
}
}
It is essential that I am able to unit test the effectiveness of the AuthorizeAttribute. We have had issues in our code base with missing attributes and incorrect scopes. This answer is exactly what I am looking for, but not having a ActionInvoker method in Microsoft.AspNetCore.Mvc.Controller means I am not able to do it this way.
Unit Test:
[Fact]
public void GetAsync_InvalidScope_ReturnsUnauthorizedResult()
{
// Arrange
var fooRepository = new StubFooRepository();
var controller = new FooController(fooRepository)
{
ControllerContext = new ControllerContext
{
HttpContext = new FakeHttpContext()
// User unfortunately not available in HttpContext
//,User = new User() { Scopes = "none" }
}
};
// Act
var result = controller.GetAsync().Result;
// Assert
Assert.IsType<UnauthorizedResult>(result);
}
How can I unit test that users without the correct scopes are denied access to my controller method?
Currently I have settled for testing merely the presence of an AuthorizeAttribute as follows, but this is really not good enough:
[Fact]
public void GetAsync_Analysis_HasAuthorizeAttribute()
{
// Arrange
var fooRepository = new StubFooRepository();
var controller = new FooController(fooRepository)
{
ControllerContext = new ControllerContext
{
HttpContext = new FakeHttpContext()
}
};
// Act
var type = controller.GetType();
var methodInfo = type.GetMethod("GetAsync", new Type[] { });
var attributes = methodInfo.GetCustomAttributes(typeof(AuthorizeAttribute), true);
// Assert
Assert.True(attributes.Any());
}
This would need integration testing with an in-memory test server because the attribute is evaluated by the framework as it processes the request pipeline.
Integration testing in ASP.NET Core
Integration testing ensures that an application's components function correctly when assembled together. ASP.NET Core supports integration testing using unit test frameworks and a built-in test web host that can be used to handle requests without network overhead.
[Fact]
public async Task GetAsync_InvalidScope_ReturnsUnauthorizedResult() {
// Arrange
var server = new TestServer(new WebHostBuilder().UseStartup<Startup>());
var client = server.CreateClient();
var url = "api/foo";
var expected = HttpStatusCode.Unauthorized;
// Act
var response = await client.GetAsync(url);
// Assert
Assert.AreEqual(expected, response.StatusCode);
}
You can also create a start up specifically for the test that will replace any dependencies for DI with stubs/mocks if you do not want the test hitting actual production implementations.
What you could do, is to configure your testserver to add an anonymous filter middleware:
private HttpClient CreatControllerClient()
{
return _factory.WithWebHostBuilder(builder
=> builder.ConfigureTestServices(services =>
{
// allow anonymous access to bypass authorization
services.AddMvc(opt => opt.Filters.Add(new AllowAnonymousFilter()));
})).CreateClient();
}
First remeove IAuthorizationHandler
var authorizationDescriptor = services.FirstOrDefault(d => d.ServiceType == typeof(IAuthorizationHandler));
if (authorizationDescriptor != null)
services.Remove(authorizationDescriptor);
Then add
services.AddScoped<IAuthorizationHandler, TestAllowAnonymous>();
public class TestAllowAnonymous : IAuthorizationHandler
{
public Task HandleAsync(AuthorizationHandlerContext context)
{
foreach (IAuthorizationRequirement requirement in context.PendingRequirements.ToList())
context.Succeed(requirement); //Simply pass all requirementsreturn Task.CompletedTask;
return Task.CompletedTask;
}
}

Returning mocked object from partially mocked object not working

I'm writing a unit test where I'm trying to partially mock a service. What I mean is I want one of the methods of the service to return a different mocked object and another method to behave as normal. This is the method I'm testing:
public async Task<List<string>> GetDeletedRecordIds<T>(DateTime startDate)
where T : ISalesForceObject
{
List<string> result;
try
{
var client = await this.GetForceClient();
var init = await client.GetDeleted<DeletedRecordRootObject>(typeof(T).Name, startDate, DateTime.Now);
result = init?.DeletedRecords.Select(d => d.Id).ToList();
}
catch (Exception e)
{
this._logger.LogError(LoggingEvents.GENERAL_ERROR, e, "GetDeletedRecordIds");
throw;
}
return result;
}
This is the method that I need to return a mocked object:
public async Task<IForceClient> GetForceClient()
{
ForceClient forceClient = null;
try
{
var auth = new AuthenticationClient();
var consumerKey = this._settingService.GetSetting("SalesForceConsumerKey");
var consumerSecret = this._settingService.GetSetting("SalesForceConsumerSecret");
var password = this._settingService.GetSetting("SalesForcePassword");
var securityToken = this._settingService.GetSetting("SalesForceSecurityToken");
var username = this._settingService.GetSetting("SalesForceUsername");
var tokenUrl = $"{this._settingService.GetSetting("SalesForceUrl")}/services/oauth2/token";
await auth.UsernamePasswordAsync(
consumerKey,
consumerSecret,
username,
password + securityToken,
tokenUrl);
forceClient = new ForceClient(auth.InstanceUrl, auth.AccessToken, auth.ApiVersion);
}
catch (Exception e)
{
this._logger.LogError(LoggingEvents.GENERAL_ERROR, e, $"GetForceClient");
throw;
}
return forceClient;
}
And this is what I currently have in my unit test:
var mockForceClient = new Mock<IForceClient>();
mockForceClient
.Setup(
i => i.GetDeleted<DeletedRecordRootObject>(
It.IsAny<string>(),
It.IsAny<DateTime>(),
It.IsAny<DateTime>())).ReturnsAsync(deletedRecordRootObject);
var mockService = new Mock<IForceDotComService>();
mockService.Setup(m => m.GetDeletedRecordIds<sf.Account>(It.IsAny<DateTime>()))
.Returns(async (DateTime d) => await this._service.GetDeletedRecordIds<sf.Account>(d));
mockService.Setup(m => m.GetForceClient())
.ReturnsAsync(mockForceClient.Object);
Currently, the test runs in GetDeletedRecordIds until it hits the call to the GetForceClient method. Then instead of returning the mocked ForceClient object, it actually tries to run the method which of course fails.
Thanks in advance for any help.
SOLUTION:
Here's how I solved my problem.
First, I created a service to return the ForceClient as follows:
public class ForceClientService : IForceClientService
{
private readonly ILogger _logger;
private readonly ISettingService _settingService;
public ForceClientService(
ILogger<ForceClientService> logger,
ISettingService settingService)
{
this._logger = logger;
this._settingService = settingService;
}
public async Task<IForceClient> GetForceClient()
{
ForceClient forceClient = null;
try
{
var auth = new AuthenticationClient();
var consumerKey = this._settingService.GetSetting("SalesForceConsumerKey");
var consumerSecret = this._settingService.GetSetting("SalesForceConsumerSecret");
var password = this._settingService.GetSetting("SalesForcePassword");
var securityToken = this._settingService.GetSetting("SalesForceSecurityToken");
var username = this._settingService.GetSetting("SalesForceUsername");
var tokenUrl = $"{this._settingService.GetSetting("SalesForceUrl")}/services/oauth2/token";
await auth.UsernamePasswordAsync(
consumerKey,
consumerSecret,
username,
password + securityToken,
tokenUrl);
forceClient = new ForceClient(auth.InstanceUrl, auth.AccessToken, auth.ApiVersion);
}
catch (Exception e)
{
this._logger.LogError(LoggingEvents.GENERAL_ERROR, e, $"GetForceClient");
throw;
}
return forceClient;
}
}
Then I changed the method I am testing:
public async Task DeleteRecord<TSf>(TSf record)
where TSf : ISalesForceObject
{
try
{
var client = await this._forceClientService.GetForceClient();
var response = await client.DeleteAsync(typeof(TSf).Name, record.Id);
if (!response)
{
throw new Exception($"Error deleting record with ID {record.Id}");
}
}
catch (Exception e)
{
this._logger.LogError(LoggingEvents.GENERAL_ERROR, e, $"ForceDotComService.DeleteRecord");
throw;
}
}
Then I rebuilt my mock to mock the dependencies vs. the methods:
var mockForceClient = new Mock<IForceClient>();
mockForceClient
.Setup(
i => i.GetDeleted<DeletedRecordRootObject>(
It.IsAny<string>(),
It.IsAny<DateTime>(),
It.IsAny<DateTime>())).ReturnsAsync(deletedRecordRootObject);
var mockLogger = new Mock<ILogger<ForceDotComService>>();
var mockForceClientService = new Mock<IForceClientService>();
mockForceClientService.Setup(m => m.GetForceClient()).ReturnsAsync(mockForceClient.Object);
this._service = new ForceDotComService(mockLogger.Object, mockForceClientService.Object);
It is now working as expected. Thanks so much for the help!
Extract this.GetForceClient() out into its own service backed by an abstraction
public IForceClientProvider {
Task<IForceClient> GetForceClient();
}
you would then refactor your current class under test to explicitly depend on that interface via constructor injection.
public class ForceDotComService : IForceDotComService {
private readonly IForceClientProvider provider;
public ForceDotComService(IForceClientProvider provider) {
this.provider = provider;
}
public async Task<List<string>> GetDeletedRecordIds<T>(DateTime startDate)
where T : ISalesForceObject {
List<string> result;
try {
var client = await provider.GetForceClient();
var init = await client.GetDeleted<DeletedRecordRootObject>(typeof(T).Name, startDate, DateTime.Now);
result = init?.DeletedRecords.Select(d => d.Id).ToList();
} catch (Exception e) {
this._logger.LogError(LoggingEvents.GENERAL_ERROR, e, "GetDeletedRecordIds");
throw;
}
return result;
}
}
This would then allow you to mock the desired behavior when testing. In implementation code you would have the same code presented above in the GetForceClient() method.
You mock dependencies, not methods on the class under test.
You need to inject the dependency IForceClient, for example by making it a constructor parameter. Because now your GetForceClient() is simply being called on the class under test, which runs in that class and not on your mock, and so simply returns the new ForceClient() stated in there.

Unit testing code using Flurl GetResponseJson<TError>()

I'm trying to unit test a controller that is catching a FlurlHttpException and calling GetResponseJson<TError>() to get the error message in the catch block. I attempted to mock the exception, but the Call property does not allow me set the Settings. When the unit test runs it fails because there isn't a JsonSerializer in the settings. How do I setup this test?
Here's my current attempt that does not work:
Controller
[Route]
public async Task<IHttpActionResult> Post(SomeModel model)
{
try
{
var id = await _serviceClient.Create(model);
return Ok(new { id });
}
catch (FlurlHttpException ex)
{
if (ex.Call.HttpStatus == HttpStatusCode.BadRequest)
return BadRequest(ex.GetResponseJson<BadRequestError>().Message);
throw;
}
}
Unit Test
[TestMethod]
public async Task Post_ServiceClientBadRequest_ShouldReturnBadRequestWithMessage()
{
//Arrange
string errorMessage = "A bad request";
string jsonErrorResponse = JsonConvert.SerializeObject(new BadRequestError { Message = errorMessage });
var badRequestCall = new HttpCall
{
Response = new HttpResponseMessage(HttpStatusCode.BadRequest),
ErrorResponseBody = jsonErrorResponse
//This would work, but Settings has a private set, so I can't
//,Settings = new FlurlHttpSettings { JsonSerializer = new NewtonsoftJsonSerializer(new JsonSerializerSettings()) }
};
_mockServiceClient
.Setup(client => client.create(It.IsAny<SomeModel>()))
.ThrowsAsync(new FlurlHttpException(badRequestCall, "exception", new Exception()));
//Act
var result = await _controller.Post(new SomeModel());
var response = result as BadRequestErrorMessageResult;
//Assert
Assert.IsNotNull(response);
Assert.AreEqual(errorMessage, response.Message);
}
If you are encapsulating the usage of Flurl within your ServiceClient object, then I think catching FlurlException, extracting Message, and returning a more appropriate exception should also be encapsulated in that service. This will make your controller much easier to test.

Create Visual Studio Unit Tests For ASP.NET [duplicate]

I have a web service I am trying to unit test. In the service it pulls several values from the HttpContext like so:
m_password = (string)HttpContext.Current.Session["CustomerId"];
m_userID = (string)HttpContext.Current.Session["CustomerUrl"];
in the unit test I am creating the context using a simple worker request, like so:
SimpleWorkerRequest request = new SimpleWorkerRequest("", "", "", null, new StringWriter());
HttpContext context = new HttpContext(request);
HttpContext.Current = context;
However, whenever I try to set the values of HttpContext.Current.Session
HttpContext.Current.Session["CustomerId"] = "customer1";
HttpContext.Current.Session["CustomerUrl"] = "customer1Url";
I get null reference exception that says HttpContext.Current.Session is null.
Is there any way to initialize the current session within the unit test?
You can "fake it" by creating a new HttpContext like this:
http://www.necronet.org/archive/2010/07/28/unit-testing-code-that-uses-httpcontext-current-session.aspx
I've taken that code and put it on an static helper class like so:
public static HttpContext FakeHttpContext()
{
var httpRequest = new HttpRequest("", "http://example.com/", "");
var stringWriter = new StringWriter();
var httpResponse = new HttpResponse(stringWriter);
var httpContext = new HttpContext(httpRequest, httpResponse);
var sessionContainer = new HttpSessionStateContainer("id", new SessionStateItemCollection(),
new HttpStaticObjectsCollection(), 10, true,
HttpCookieMode.AutoDetect,
SessionStateMode.InProc, false);
httpContext.Items["AspSession"] = typeof(HttpSessionState).GetConstructor(
BindingFlags.NonPublic | BindingFlags.Instance,
null, CallingConventions.Standard,
new[] { typeof(HttpSessionStateContainer) },
null)
.Invoke(new object[] { sessionContainer });
return httpContext;
}
Or instead of using reflection to construct the new HttpSessionState instance, you can just attach your HttpSessionStateContainer to the HttpContext (as per Brent M. Spell's comment):
SessionStateUtility.AddHttpSessionStateToContext(httpContext, sessionContainer);
and then you can call it in your unit tests like:
HttpContext.Current = MockHelper.FakeHttpContext();
We had to mock HttpContext by using a HttpContextManager and calling the factory from within our application as well as the Unit Tests
public class HttpContextManager
{
private static HttpContextBase m_context;
public static HttpContextBase Current
{
get
{
if (m_context != null)
return m_context;
if (HttpContext.Current == null)
throw new InvalidOperationException("HttpContext not available");
return new HttpContextWrapper(HttpContext.Current);
}
}
public static void SetCurrentContext(HttpContextBase context)
{
m_context = context;
}
}
You would then replace any calls to HttpContext.Current with HttpContextManager.Current and have access to the same methods. Then when you're testing, you can also access the HttpContextManager and mock your expectations
This is an example using Moq:
private HttpContextBase GetMockedHttpContext()
{
var context = new Mock<HttpContextBase>();
var request = new Mock<HttpRequestBase>();
var response = new Mock<HttpResponseBase>();
var session = new Mock<HttpSessionStateBase>();
var server = new Mock<HttpServerUtilityBase>();
var user = new Mock<IPrincipal>();
var identity = new Mock<IIdentity>();
var urlHelper = new Mock<UrlHelper>();
var routes = new RouteCollection();
MvcApplication.RegisterRoutes(routes);
var requestContext = new Mock<RequestContext>();
requestContext.Setup(x => x.HttpContext).Returns(context.Object);
context.Setup(ctx => ctx.Request).Returns(request.Object);
context.Setup(ctx => ctx.Response).Returns(response.Object);
context.Setup(ctx => ctx.Session).Returns(session.Object);
context.Setup(ctx => ctx.Server).Returns(server.Object);
context.Setup(ctx => ctx.User).Returns(user.Object);
user.Setup(ctx => ctx.Identity).Returns(identity.Object);
identity.Setup(id => id.IsAuthenticated).Returns(true);
identity.Setup(id => id.Name).Returns("test");
request.Setup(req => req.Url).Returns(new Uri("http://www.google.com"));
request.Setup(req => req.RequestContext).Returns(requestContext.Object);
requestContext.Setup(x => x.RouteData).Returns(new RouteData());
request.SetupGet(req => req.Headers).Returns(new NameValueCollection());
return context.Object;
}
and then to use it within your unit tests, I call this within my Test Init method
HttpContextManager.SetCurrentContext(GetMockedHttpContext());
you can then, in the above method add the expected results from Session that you're expecting to be available to your web service.
Milox solution is better than the accepted one IMHO but I had some problems with this implementation when handling urls with querystring.
I made some changes to make it work properly with any urls and to avoid Reflection.
public static HttpContext FakeHttpContext(string url)
{
var uri = new Uri(url);
var httpRequest = new HttpRequest(string.Empty, uri.ToString(),
uri.Query.TrimStart('?'));
var stringWriter = new StringWriter();
var httpResponse = new HttpResponse(stringWriter);
var httpContext = new HttpContext(httpRequest, httpResponse);
var sessionContainer = new HttpSessionStateContainer("id",
new SessionStateItemCollection(),
new HttpStaticObjectsCollection(),
10, true, HttpCookieMode.AutoDetect,
SessionStateMode.InProc, false);
SessionStateUtility.AddHttpSessionStateToContext(
httpContext, sessionContainer);
return httpContext;
}
I worte something about this a while ago.
Unit Testing HttpContext.Current.Session in MVC3 .NET
Hope it helps.
[TestInitialize]
public void TestSetup()
{
// We need to setup the Current HTTP Context as follows:
// Step 1: Setup the HTTP Request
var httpRequest = new HttpRequest("", "http://localhost/", "");
// Step 2: Setup the HTTP Response
var httpResponce = new HttpResponse(new StringWriter());
// Step 3: Setup the Http Context
var httpContext = new HttpContext(httpRequest, httpResponce);
var sessionContainer =
new HttpSessionStateContainer("id",
new SessionStateItemCollection(),
new HttpStaticObjectsCollection(),
10,
true,
HttpCookieMode.AutoDetect,
SessionStateMode.InProc,
false);
httpContext.Items["AspSession"] =
typeof(HttpSessionState)
.GetConstructor(
BindingFlags.NonPublic | BindingFlags.Instance,
null,
CallingConventions.Standard,
new[] { typeof(HttpSessionStateContainer) },
null)
.Invoke(new object[] { sessionContainer });
// Step 4: Assign the Context
HttpContext.Current = httpContext;
}
[TestMethod]
public void BasicTest_Push_Item_Into_Session()
{
// Arrange
var itemValue = "RandomItemValue";
var itemKey = "RandomItemKey";
// Act
HttpContext.Current.Session.Add(itemKey, itemValue);
// Assert
Assert.AreEqual(HttpContext.Current.Session[itemKey], itemValue);
}
You can try FakeHttpContext:
using (new FakeHttpContext())
{
HttpContext.Current.Session["CustomerId"] = "customer1";
}
If you're using the MVC framework, this should work. I used Milox's FakeHttpContext and added a few additional lines of code. The idea came from this post:
http://codepaste.net/p269t8
This seems to work in MVC 5. I haven't tried this in earlier versions of MVC.
HttpContext.Current = MockHttpContext.FakeHttpContext();
var wrapper = new HttpContextWrapper(HttpContext.Current);
MyController controller = new MyController();
controller.ControllerContext = new ControllerContext(wrapper, new RouteData(), controller);
string result = controller.MyMethod();
In asp.net Core / MVC 6 rc2 you can set the HttpContext
var SomeController controller = new SomeController();
controller.ControllerContext = new ControllerContext();
controller.ControllerContext.HttpContext = new DefaultHttpContext();
controller.HttpContext.Session = new DummySession();
rc 1 was
var SomeController controller = new SomeController();
controller.ActionContext = new ActionContext();
controller.ActionContext.HttpContext = new DefaultHttpContext();
controller.HttpContext.Session = new DummySession();
https://stackoverflow.com/a/34022964/516748
Consider using Moq
new Mock<ISession>();
The answer that worked with me is what #Anthony had written, but you have to add another line which is
request.SetupGet(req => req.Headers).Returns(new NameValueCollection());
so you can use this:
HttpContextFactory.Current.Request.Headers.Add(key, value);
Try this:
// MockHttpSession Setup
var session = new MockHttpSession();
// MockHttpRequest Setup - mock AJAX request
var httpRequest = new Mock<HttpRequestBase>();
// Setup this part of the HTTP request for AJAX calls
httpRequest.Setup(req => req["X-Requested-With"]).Returns("XMLHttpRequest");
// MockHttpContextBase Setup - mock request, cache, and session
var httpContext = new Mock<HttpContextBase>();
httpContext.Setup(ctx => ctx.Request).Returns(httpRequest.Object);
httpContext.Setup(ctx => ctx.Cache).Returns(HttpRuntime.Cache);
httpContext.Setup(ctx => ctx.Session).Returns(session);
// MockHttpContext for cache
var contextRequest = new HttpRequest("", "http://localhost/", "");
var contextResponse = new HttpResponse(new StringWriter());
HttpContext.Current = new HttpContext(contextRequest, contextResponse);
// MockControllerContext Setup
var context = new Mock<ControllerContext>();
context.Setup(ctx => ctx.HttpContext).Returns(httpContext.Object);
//TODO: Create new controller here
// Set controller's ControllerContext to context.Object
And Add the class:
public class MockHttpSession : HttpSessionStateBase
{
Dictionary<string, object> _sessionDictionary = new Dictionary<string, object>();
public override object this[string name]
{
get
{
return _sessionDictionary.ContainsKey(name) ? _sessionDictionary[name] : null;
}
set
{
_sessionDictionary[name] = value;
}
}
public override void Abandon()
{
var keys = new List<string>();
foreach (var kvp in _sessionDictionary)
{
keys.Add(kvp.Key);
}
foreach (var key in keys)
{
_sessionDictionary.Remove(key);
}
}
public override void Clear()
{
var keys = new List<string>();
foreach (var kvp in _sessionDictionary)
{
keys.Add(kvp.Key);
}
foreach(var key in keys)
{
_sessionDictionary.Remove(key);
}
}
}
This will allow you to test with both session and cache.
I was looking for something a little less invasive than the options mentioned above. In the end I came up with a cheesy solution, but it might get some folks moving a little faster.
First I created a TestSession class:
class TestSession : ISession
{
public TestSession()
{
Values = new Dictionary<string, byte[]>();
}
public string Id
{
get
{
return "session_id";
}
}
public bool IsAvailable
{
get
{
return true;
}
}
public IEnumerable<string> Keys
{
get { return Values.Keys; }
}
public Dictionary<string, byte[]> Values { get; set; }
public void Clear()
{
Values.Clear();
}
public Task CommitAsync()
{
throw new NotImplementedException();
}
public Task LoadAsync()
{
throw new NotImplementedException();
}
public void Remove(string key)
{
Values.Remove(key);
}
public void Set(string key, byte[] value)
{
if (Values.ContainsKey(key))
{
Remove(key);
}
Values.Add(key, value);
}
public bool TryGetValue(string key, out byte[] value)
{
if (Values.ContainsKey(key))
{
value = Values[key];
return true;
}
value = new byte[0];
return false;
}
}
Then I added an optional parameter to my controller's constructor. If the parameter is present, use it for session manipulation. Otherwise, use the HttpContext.Session:
class MyController
{
private readonly ISession _session;
public MyController(ISession session = null)
{
_session = session;
}
public IActionResult Action1()
{
Session().SetString("Key", "Value");
View();
}
public IActionResult Action2()
{
ViewBag.Key = Session().GetString("Key");
View();
}
private ISession Session()
{
return _session ?? HttpContext.Session;
}
}
Now I can inject my TestSession into the controller:
class MyControllerTest
{
private readonly MyController _controller;
public MyControllerTest()
{
var testSession = new TestSession();
var _controller = new MyController(testSession);
}
}
The answer #Ro Hit gave helped me a lot, but I was missing the user credentials because I had to fake a user for authentication unit testing. Hence, let me describe how I solved it.
According to this, if you add the method
// using System.Security.Principal;
GenericPrincipal FakeUser(string userName)
{
var fakeIdentity = new GenericIdentity(userName);
var principal = new GenericPrincipal(fakeIdentity, null);
return principal;
}
and then append
HttpContext.Current.User = FakeUser("myDomain\\myUser");
to the last line of the TestSetup method you're done, the user credentials are added and ready to be used for authentication testing.
I also noticed that there are other parts in HttpContext you might require, such as the .MapPath() method. There is a FakeHttpContext available, which is described here and can be installed via NuGet.
I found the following simple solution for specifying a user in the HttpContext: https://forums.asp.net/post/5828182.aspx
Never mock.. never! The solution is pretty simple. Why fake such a beautiful creation like HttpContext?
Push the session down! (Just this line is enough for most of us to understand but explained in detail below)
(string)HttpContext.Current.Session["CustomerId"]; is how we access it now. Change this to
_customObject.SessionProperty("CustomerId")
When called from test, _customObject uses alternative store (DB or cloud key value[ http://www.kvstore.io/] )
But when called from the real application, _customObject uses Session.
how is this done? well... Dependency Injection!
So test can set the session(underground) and then call the application method as if it knows nothing about the session. Then test secretly checks if the application code correctly updated the session. Or if the application behaves based on the session value set by the test.
Actually, we did end up mocking even though I said: "never mock". Becuase we couldn't help but slip to the next rule, "mock where it hurts the least!". Mocking huge HttpContext or mocking a tiny session, which hurts the least? don't ask me where these rules came from. Let us just say common sense. Here is an interesting read on not mocking as unit test can kills us
Try this way..
public static HttpContext getCurrentSession()
{
HttpContext.Current = new HttpContext(new HttpRequest("", ConfigurationManager.AppSettings["UnitTestSessionURL"], ""), new HttpResponse(new System.IO.StringWriter()));
System.Web.SessionState.SessionStateUtility.AddHttpSessionStateToContext(
HttpContext.Current, new HttpSessionStateContainer("", new SessionStateItemCollection(), new HttpStaticObjectsCollection(), 20000, true,
HttpCookieMode.UseCookies, SessionStateMode.InProc, false));
return HttpContext.Current;
}

Categories

Resources