Mocking IPrincipal in ASP.NET Core - c#

I have an ASP.NET MVC Core application that I am writing unit tests for. One of the action methods uses User name for some functionality:
SettingsViewModel svm = _context.MySettings(User.Identity.Name);
which obviously fails in the unit test. I looked around and all suggestions are from .NET 4.5 to mock HttpContext. I am sure there is a better way to do that. I tried to inject IPrincipal, but it threw an error; and I even tried this (out of desperation, I suppose):
public IActionResult Index(IPrincipal principal = null) {
IPrincipal user = principal ?? User;
SettingsViewModel svm = _context.MySettings(user.Identity.Name);
return View(svm);
}
but this threw an error as well.
Couldn't find anything in the docs either...

The controller’s User is accessed through the HttpContext of the controller. The latter is stored within the ControllerContext.
The easiest way to set the user is by assigning a different HttpContext with a constructed user. We can use DefaultHttpContext for this purpose, that way we don’t have to mock everything. Then we just use that HttpContext within a controller context and pass that to the controller instance:
var user = new ClaimsPrincipal(new ClaimsIdentity(new Claim[]
{
new Claim(ClaimTypes.Name, "example name"),
new Claim(ClaimTypes.NameIdentifier, "1"),
new Claim("custom-claim", "example claim value"),
}, "mock"));
var controller = new SomeController(dependencies…);
controller.ControllerContext = new ControllerContext()
{
HttpContext = new DefaultHttpContext() { User = user }
};
When creating your own ClaimsIdentity, make sure to pass an explicit authenticationType to the constructor. This makes sure that IsAuthenticated will work correctly (in case you use that in your code to determine whether a user is authenticated).

In previous versions you could have set User directly on the controller, which made for some very easy unit tests.
If you look at the source code for ControllerBase you will notice that the User is extracted from HttpContext.
/// <summary>
/// Gets the <see cref="ClaimsPrincipal"/> for user associated with the executing action.
/// </summary>
public ClaimsPrincipal User => HttpContext?.User;
and the controller accesses the HttpContext via ControllerContext
/// <summary>
/// Gets the <see cref="Http.HttpContext"/> for the executing action.
/// </summary>
public HttpContext HttpContext => ControllerContext.HttpContext;
You will notice that these two are read only properties. The good news is that ControllerContext property allows for setting it's value so that will be your way in.
So the target is to get at that object. In Core HttpContext is abstract so it is a lot easier to mock.
Assuming a controller like
public class MyController : Controller {
IMyContext _context;
public MyController(IMyContext context) {
_context = context;
}
public IActionResult Index() {
SettingsViewModel svm = _context.MySettings(User.Identity.Name);
return View(svm);
}
//...other code removed for brevity
}
Using Moq, a test could look like this
public void Given_User_Index_Should_Return_ViewResult_With_Model() {
//Arrange
var username = "FakeUserName";
var identity = new GenericIdentity(username, "");
var mockPrincipal = new Mock<ClaimsPrincipal>();
mockPrincipal.Setup(x => x.Identity).Returns(identity);
mockPrincipal.Setup(x => x.IsInRole(It.IsAny<string>())).Returns(true);
var mockHttpContext = new Mock<HttpContext>();
mockHttpContext.Setup(m => m.User).Returns(mockPrincipal.Object);
var model = new SettingsViewModel() {
//...other code removed for brevity
};
var mockContext = new Mock<IMyContext>();
mockContext.Setup(m => m.MySettings(username)).Returns(model);
var controller = new MyController(mockContext.Object) {
ControllerContext = new ControllerContext {
HttpContext = mockHttpContext.Object
}
};
//Act
var viewResult = controller.Index() as ViewResult;
//Assert
Assert.IsNotNull(viewResult);
Assert.IsNotNull(viewResult.Model);
Assert.AreEqual(model, viewResult.Model);
}

There is also the possibility to use the existing classes, and mock only when needed.
var user = new Mock<ClaimsPrincipal>();
_controller.ControllerContext = new ControllerContext
{
HttpContext = new DefaultHttpContext
{
User = user.Object
}
};

In my case, I needed to make use of Request.HttpContext.User.Identity.IsAuthenticated, Request.HttpContext.User.Identity.Name and some business logic sitting outside of the controller. I was able to use a combination of Nkosi's, Calin's and Poke's answer for this:
var identity = new Mock<IIdentity>();
identity.SetupGet(i => i.IsAuthenticated).Returns(true);
identity.SetupGet(i => i.Name).Returns("FakeUserName");
var mockPrincipal = new Mock<ClaimsPrincipal>();
mockPrincipal.Setup(x => x.Identity).Returns(identity.Object);
var mockAuthHandler = new Mock<ICustomAuthorizationHandler>();
mockAuthHandler.Setup(x => x.CustomAuth(It.IsAny<ClaimsPrincipal>(), ...)).Returns(true).Verifiable();
var controller = new MyController(...);
var mockHttpContext = new Mock<HttpContext>();
mockHttpContext.Setup(m => m.User).Returns(mockPrincipal.Object);
controller.ControllerContext = new ControllerContext();
controller.ControllerContext.HttpContext = new DefaultHttpContext()
{
User = mockPrincipal.Object
};
var result = controller.Get() as OkObjectResult;
//Assert results
mockAuthHandler.Verify();

I want to hit my Controllers directly and just use DI like AutoFac. To do this I first registering ContextController.
var identity = new GenericIdentity("Test User");
var httpContext = new DefaultHttpContext()
{
User = new GenericPrincipal(identity, null)
};
var context = new ControllerContext { HttpContext = httpContext};
builder.RegisterInstance(context);
Next I enable property injection when I register the Controllers.
builder.RegisterAssemblyTypes(assembly)
.Where(t => t.Name.EndsWith("Controller")).PropertiesAutowired();
Then User.Identity.Name is populated, and I do not need to do anything special when calling a method on my Controller.
public async Task<ActionResult<IEnumerable<Employee>>> Get()
{
var requestedBy = User.Identity?.Name;
..................

I would look to implement an Abstract Factory Pattern.
Create an interface for a factory specifically for providing user names.
Then provide concrete classes, one which provides User.Identity.Name, and one that provides some other hard coded value that works for your tests.
You can then use the appropriate concrete class depending on production versus test code. Perhaps looking to pass the factory in as a parameter, or switching to the correct factory based on some configuration value.
interface IUserNameFactory
{
string BuildUserName();
}
class ProductionFactory : IUserNameFactory
{
public BuildUserName() { return User.Identity.Name; }
}
class MockFactory : IUserNameFactory
{
public BuildUserName() { return "James"; }
}
IUserNameFactory factory;
if(inProductionMode)
{
factory = new ProductionFactory();
}
else
{
factory = new MockFactory();
}
SettingsViewModel svm = _context.MySettings(factory.BuildUserName());

I got a brownfield .net 4.8 project that I needed to convert to .net 5.0 and I wanted to keep as much of the original code as possible, including the unit-/integration tests. The test for Controllers relied on the Context a lot so I created this Extension method to enable setting tokens, claims and headers:
public static void AddContextMock(
this ControllerBase controller,
IEnumerable<(string key, string value)> claims = null,
IEnumerable<(string key, string value)> tokens = null,
IEnumerable<(string key, string value)> headers = null)
{
HttpContext mockContext = new DefaultHttpContext();
if(claims != null)
{
mockContext.User = SetupClaims(claims);
}
if(tokens != null)
{
mockContext.RequestServices = SetupTokens(tokens);
}
if(headers != null)
{
SetupHeaders(mockContext, headers);
}
controller.ControllerContext = new ControllerContext()
{
HttpContext = mockContext
};
}
private static void SetupHeaders(HttpContext mockContext, IEnumerable<(string key, string value)> headers)
{
foreach(var header in headers)
{
mockContext.Request.Headers.Add(header.key, header.value);
}
}
private static ClaimsPrincipal SetupClaims(IEnumerable<(string key, string value)> claimValues)
{
var claims = claimValues.Select(c => new Claim(c.key, c.value));
return new ClaimsPrincipal(new ClaimsIdentity(claims, "mock"));
}
private static IServiceProvider SetupTokens(IEnumerable<(string key, string value)> tokenValues)
{
var mockServiceProvider = new Mock<IServiceProvider>();
var authenticationServiceMock = new Mock<IAuthenticationService>();
var authResult = AuthenticateResult.Success(
new AuthenticationTicket(new ClaimsPrincipal(), null));
var tokens = tokenValues.Select(t => new AuthenticationToken { Name = t.key, Value = t.value });
authResult.Properties.StoreTokens(tokens);
authenticationServiceMock
.Setup(x => x.AuthenticateAsync(It.IsAny<HttpContext>(), null))
.ReturnsAsync(authResult);
mockServiceProvider.Setup(_ => _.GetService(typeof(IAuthenticationService))).Returns(authenticationServiceMock.Object);
return mockServiceProvider.Object;
}
This uses Moq but can be adapted to other mocking frameworks. The authentication type is hardcoded to "mock" since I rely on default authentication but this could be supplied as well.
It is used as such:
_controllerUnderTest.AddContextMock(
claims: new[]
{
(ClaimTypes.Name, "UserName"),
(ClaimTypes.MobilePhone, "1234"),
},
tokens: new[]
{
("access_token", "accessTokenValue")
},
headers: new[]
{
("header", "headerValue")
});

If you're using Razor pages and want to override the claims:
[SetUp]
public void Setup()
{
var user = new ClaimsPrincipal(new ClaimsIdentity(
new Claim[] {
new("dateofbirth", "2000-10-10"),
new("surname", "Smith") },
"mock"));
_razorModel = new RazorModel()
{
PageContext = new PageContext
{
HttpContext = new DefaultHttpContext() { User = user }
}
};
}

Related

xunit - how to get HttpContext.User.Identity in unit tests

I added a method to my controllers to get the user-id from the JWT token in the HttpContext. In my unit tests the HttpContext is null, so I get an exception.
How can I solve the problem? Is there a way to moq the HttpContext?
Here is the method to get the user in my base controller
protected string GetUserId()
{
if (HttpContext.User.Identity is ClaimsIdentity identity)
{
IEnumerable<Claim> claims = identity.Claims;
return claims.ToList()[0].Value;
}
return "";
}
One of my tests look like this
[Theory]
[MemberData(nameof(TestCreateUsergroupItemData))]
public async Task TestPostUsergroupItem(Usergroup usergroup)
{
// Arrange
UsergroupController controller = new UsergroupController(context, mapper);
// Act
var controllerResult = await controller.Post(usergroup).ConfigureAwait(false);
// Assert
//....
}
There really is no need to have to mock the HttpContext in this particular case.
Use the DefaultHttpContext and set the members necessary to exercise the test to completion
For example
[Theory]
[MemberData(nameof(TestCreateUsergroupItemData))]
public async Task TestPostUsergroupItem(Usergroup usergroup) {
// Arrange
//...
var identity = new GenericIdentity("some name", "test");
var contextUser = new ClaimsPrincipal(identity); //add claims as needed
//...then set user and other required properties on the httpContext as needed
var httpContext = new DefaultHttpContext() {
User = contextUser;
};
//Controller needs a controller context to access HttpContext
var controllerContext = new ControllerContext() {
HttpContext = httpContext,
};
//assign context to controller
UsergroupController controller = new UsergroupController(context, mapper) {
ControllerContext = controllerContext,
};
// Act
var controllerResult = await controller.Post(usergroup).ConfigureAwait(false);
// Assert
....
}
First of all, I would suggest you to use IHttpContextAccessor to access HttpContext and inject via Dependency Injection instead of using HttpContext directly. You can follow this Microsoft documentation to understand usage and injection of IHttpContextAccessor.
With the above code, your code will looks as follows to inject IHttpContextAccessor
private IHttpContextAccessor httpContextAccessor;
public class UsergroupController(IHttpContextAccessor httpContextAccessor, ...additional parameters)
{
this.httpContextAccessor = httpContextAccessor;
//...additional assignments
}
Once IHttpContextAccessor is injected, you can access the Identity as this.httpContextAccessor.HttpContext.User.Identity
So the GetUserId should change as
protected string GetUserId()
{
if (this.httpContextAccessor.HttpContext.User.Identity is ClaimsIdentity identity)
{
IEnumerable<Claim> claims = identity.Claims;
return claims.ToList()[0].Value;
}
return "";
}
With above change, now you can easily inject the mock of IHttpContextAccessor for unit testing. You can use the below code to create the mock:
private static ClaimsPrincipal user = new ClaimsPrincipal(
new ClaimsIdentity(
new Claim[] { new Claim("MyClaim", "MyClaimValue") },
"Basic")
);
private static Mock<IHttpContextAccessor> GetHttpContextAccessor()
{
var httpContextAccessorMock = new Mock<IHttpContextAccessor>();
httpContextAccessorMock.Setup(h => h.HttpContext.User).Returns(user);
return httpContextAccessorMock;
}
With the above setup, in your test method, you can inject the mock of IHttpContextAccessor while instantiating the object of UsergroupController.
As a follow up to the comment by #Nkosi:
there's no requirement to DI the context as you can set it during testing like so:
var identity = new GenericIdentity("some name", "test");
var contextUser = new ClaimsPrincipal(identity);
controller.ControllerContext = new ControllerContext
{
HttpContext = new DefaultHttpContext { User = contextUser }
};

How to unit test ViewComponent.Invoke()?

In ViewComponent object, HttpContext and User are read-only properties.
How to unit test such a component?
I'm using the MSTest Freamwork.
The follow properties are used in my code
Cookie
Session
User(System.Security.Principal)
public ViewViewComponentResult Invoke()
{
var vm = new SummaryViewModel();
if (User.Identity is ClaimsIdentity identity && identity.IsAuthenticated)
{
vm.IsAuthenticated = true;
vm.UserName = identity.Claims.FirstOrDefault(c => c.Type == "UserName").Value;
vm.PhotoUrl = identity.Claims.FirstOrDefault(c => c.Type == "FacePicture").Value;
}
return View(vm);
}
[TestMethod]
public void UserSummaryVcTest()
{
var component = new UserSummaryViewComponent();
var model = component.Invoke().ViewData.Model as SummaryViewModel;
Assert.AreEqual("UserName", model.UserName);
}
According to source code the ViewComponent relies on the ViewComponentContext.ViewContext to expose those read only properties, Which in turn accesses the HttpContext. That is your entry point to mock the desired values.
[TestMethod]
public void UserSummaryVcTest() {
// Arrange
var expected = "Username value";
var httpContext = new DefaultHttpContext(); //You can also Mock this
//...then set user and other required properties on the httpContext as needed
var viewContext = new ViewContext();
viewContext.HttpContext = httpContext;
var viewComponentContext = new ViewComponentContext();
viewComponentContext.ViewContext = viewContext;
var viewComponent = new UserSummaryViewComponent();
viewComponent.ViewComponentContext = viewComponentContext;
//Act
var model = viewComponent.Invoke().ViewData.Model as SummaryViewModel;
//Assert
Assert.AreEqual(expected, model.UserName);
}
Here is just a samle for async,
[TestMethod]
public async System.Threading.Tasks.Task InvokeAsyncNameAsync()
{
# setup mocks
...
var httpContext = new DefaultHttpContext();
var viewContext = new ViewContext();
viewContext.HttpContext = httpContext;
var viewComponentContext = new ViewComponentContext();
viewComponentContext.ViewContext = viewContext;
var footerComponent = CreateComponentInstance();
footerComponent.ViewComponentContext = viewComponentContext;
ViewViewComponentResult result = await footerComponent.InvokeAsync() as ViewViewComponentResult;
FooterModel resultModel = (FooterModel)result.ViewData.Model;
....
# do your asserts verifications
Assert.AreEqual(expectedTest, resultModel.FooterText);
}

ASP MVC Unit Testing GetUserId()

I have a controller, where I'm calling a userId:
var userId = User.Identity.GetUserId();
This makes my unit test fail, since User is null.
I've tried to set a user in the test method using this method, but User is still null in the controller when the test runs.
var context = new Mock<HttpContextBase>();
var mockIdentity = new Mock<IIdentity>();
context.SetupGet(x => x.User.Identity).Returns(mockIdentity.Object);
mockIdentity.Setup(u => u.Name).Returns("test_userName");
Any hints as to what I'm doing wrong?
The HttpContextBase.User is of type IPrincipal and you have not mocked it. That's why it is returning null when you access it in tests. You can mock it as follows
var controllerContext = new Mock<ControllerContext>();
var principal = new Moq.Mock<IPrincipal>();
principal.Setup(p => p.IsInRole("Administrator")).Returns(true);
principal.SetupGet(x => x.Identity.Name).Returns(userName);
controllerContext.SetupGet(x => x.HttpContext.User).Returns(principal.Object);
controller.ControllerContext = controllerContext.Object;
Here is the reference of this code
Okay, so according to this post, you can do it like this:
public class MyController: Controller
{
public Func<string> GetUserId; //For testing
public MyController()
{
GetUserId = () => User.Identity.GetUserId();
}
//controller actions
}
And then instead of caling User.Identity.GetUserId() when you want the user, you just call GetUserId() and mock out the user id in your test:
controller = new MyController()
{
GetUserId = () => "IdOfYourChoosing"
};
You are mocking HttpContextBase but are not passing it to your controller.
Try this pattern:
Class:
public MyClass
{
private readonly HttpContextBase _contextBase;
public MyClass(HttpContextBase contextBase)
{
this._contextBase = contextBase;
}
public void Process()
{
var userId = _contextBase.User.Identity;
}
}
Test:
[Test]
public void MyClass_Test_SO()
{
// arrange
var context = new Mock<HttpContextBase>();
var mockIdentity = new Mock<IIdentity>();
context.SetupGet(x => x.User.Identity).Returns(mockIdentity.Object);
mockIdentity.Setup(u => u.Name).Returns("test_userName");
var sut = new MyClass(context.Object);
// act
sut.Process();
// assert
// ... whatever
}

How to mock HttpContext in a ShoppingCart controller/model

In our MVC4 application with Entity Framework 4.0 based on the Music Store Tutorial we are using Moq to mock the DbContext and unit test are logic. One of our methods proves difficult to test though since it makes use of HttpContext or HttpContextBase. One example method looks like this:
public static ShoppingCart GetCart(HttpContextBase context)
{
var cart = new ShoppingCart();
cart.ShoppingCartId = cart.GetCartId(context);
return cart;
}
The only property collected from HttpContextBase is the [CartSessionKey] as can be seen here:
public string GetCartId(HttpContextBase context)
{
if (context.Session[CartSessionKey] == null)
{
if (!string.IsNullOrWhiteSpace(context.User.Identity.Name))
{
context.Session[CartSessionKey] =
context.User.Identity.Name;
}
else
{
// Generate a new random GUID using System.Guid class
Guid tempCartId = Guid.NewGuid();
// Send tempCartId back to client as a cookie
context.Session[CartSessionKey] = tempCartId.ToString();
}
}
return context.Session[CartSessionKey].ToString();
}
We have heard horror stories that HttpContext is a very complex class and that if you print it you have enough paper to circle the earth eight times.
Nevertheless we want to mock it. The question is how. The properties that we want to mock are the [CartSessionKey], and the property that come from the context as contest.User.Identity.Name.
We suspect we need to use something like this:
var mockData = new Mock<FakeContext>();
mockData.Setup(m => m.Orders).Returns(memoryOrderItems);
mockData.Setup(m => m.Carts).Returns(memoryCartItems);
Mock<HttpContextBase> mockHttpContext = new Mock<HttpContextBase>();
Mock<HttpRequestBase> mockHttpRequest = new Mock<HttpRequestBase>();
mockHttpRequest.Setup(x => x.CartSessionKey).Returns(1);
mockHttpContext.Setup(x => x.Request).Returns(mockHttpRequest.Object);
but we cannot find how to specifically implement this so we do not get any errors on methods that use context.Session[CartSessionKey] or context.User.Identity.Name.
We hope someone can help us out.
/edit
When we do this:
var memoryUserItems = new FakeDbSet<User>()
{
new User { Email = "test#test.de",
FullName = "Test Person",
isAvailable = true,
Name = "WHat"
},
new User { Email = "test2#test.de",
FullName = "Test Person 2",
isAvailable = true,
Name = "WHat 2"
}
};
(...) Other memory...Items
And then this:
// Create mock units of work
var mockData = new Mock<FakeContext>();
mockData.Setup(m => m.Orders).Returns(memoryOrderItems);
mockData.Setup(m => m.Carts).Returns(memoryCartItems);
mockData.Setup(m => m.Users).Returns(memoryUserItems);
var principalMock = new Mock<IPrincipal>();
var identityMock = new Mock<IIdentity>();
var userMock =
identityMock.Setup(x => x.Name).Returns("Test!");
identityMock.Setup(x => x.IsAuthenticated).Returns(true); // optional ;)
mockData.Setup(x => x.Identity).Returns(identityMock.Object);
var httpReqBase = new Mock<HttpRequestBase>(); // this is useful if you want to test Ajax request checks or cookies in the controller.
var httpContextBase = new Mock<HttpContextBase>();
httpContextBase.Setup(x => x.User).Returns(principalMock.Object);
httpContextBase.Setup(x => x.Session[It.IsAny<string>()]).Returns(1); //Here is the session indexer. You can swap 'any' string for specific string.
httpContextBase.Setup(x => x.Request).Returns(httpReqBase.Object);
We get the error that:
Error 3 'project.Models.FakeContext' does
not contain a definition for 'Identity' and no extension method
'Identity' accepting a first argument of type
'project.Models.FakeContext' could be found
(are you missing a using directive or an assembly
reference?)
/ edit2
To make it more clear. The actual method I am testing is the following:
public ActionResult Complete(int id)
{
// Make sure that user is currentuser and otherwise bring user to our Thief page
if (id != db.GetCurrentUserId())
{
return View("Thief");
}
var cart = ShoppingCart.GetCart(this.HttpContext);
var currentDate = DateTime.Today;
var viewModel = new ShoppingCartViewModel
{
CartItems = cart.GetCartItems(),
CartTotal = cart.GetTotal(),
ProductItems = db.Products.ToList()
};
if (viewModel.CartItems.Count() == 0)
{
return View("Empty");
}
// Try to write cart to order table
try
{
foreach (var item in viewModel.CartItems)
{
ProcessOrder(item, id, currentDate);
}
// after this we empty the shopping cart
cart.EmptyCart();
return View();
}
catch
{
// Invalid - display error page
return View("Error");
}
}
As can be seen the var cart = ShoppingCart.GetCart(this.HttpContext); uses this.HttpContext. In the test I just do controller.Complete(1). I cannot pass a new HttpContext to the controller I guess?
/ edit 3
While using the code below with the mocks I get the following message:
Test Name: TestCheckoutCompleteShouldWithEmptyCart
Test FullName: Controllers.CheckoutControllerTest.TestCheckoutCompleteShouldWithEmptyCart
Test Source: Controllers\CheckoutControllerTest.cs : line 141
Test Outcome: Failed
Test Duration: 0:00:00.0158591
Result Message:
Test method Controllers.CheckoutControllerTest.TestCheckoutCompleteShouldWithEmptyCart threw exception:
System.NullReferenceException: Object reference not set to an instance of an object.
Result StackTrace:
at Models\ShoppingCart.cs:line 170
at \Models\ShoppingCart.cs:line 20
at \Controllers\CheckoutController.cs:line 48
at Controllers\CheckoutControllerTest.cs:line 143
OK, here it goes. The following works in MVC5 with AD, I'm not sure if it's fully backwards compatible, you'll have to check.
var principalMock = new Mock<IPrincipal>();
var identityMock = new Mock<IIdentity>();
identityMock.Setup(x => x.Name).Returns("Test!");
identityMock.Setup(x => x.IsAuthenticated).Returns(true); // optional ;)
userMock.Setup(x => x.Identity).Returns(identityMock.Object);
var httpReqBase = new Mock<HttpRequestBase>(); // this is useful if you want to test Ajax request checks or cookies in the controller.
var httpContextBase = new Mock<HttpContextBase>();
httpContextBase.Setup(x => x.User).Returns(principalMock.Object);
httpContextBase.Setup(x => x.Session[It.IsAny<string>()]).Returns(1); //Here is the session indexer. You can swap 'any' string for specific string.
httpContextBase.Setup(x => x.Request).Returns(httpReqBase.Object);
This would help you to write a proper Unit Test using Moq.
[TestClass]
public class SutTest
{
[TestMethod]
public void GetCartId_WhenUserNameIsNotNull_SessionContainsUserName()
{
var httpContextStub = new Mock<HttpContextBase>();
var httpSessionStub = new Mock<ISessionSettings>();
httpSessionStub.Setup(x => x.Get<string>(It.IsAny<string>())).Returns(() => null);
httpSessionStub.SetupSequence(x => x.Get<string>(It.IsAny<string>()))
.Returns(null)
.Returns("FakeName");
var httpUserStub = new Mock<IPrincipal>();
var httpIdenttyStub = new Mock<IIdentity>();
httpUserStub.SetupGet(x => x.Identity).Returns(httpIdenttyStub.Object);
httpIdenttyStub.SetupGet(x => x.Name).Returns("FakeName");
httpContextStub.Setup(x => x.User).Returns(httpUserStub.Object);
var sut = new Sut(httpSessionStub.Object);
var result = sut.GetCartId(httpContextStub.Object);
Assert.AreEqual("FakeName",result );
}
}
Check the SetupSequence method which gives you find Control over different values being return on he same stubbed call.
Also important to decouple your session from HttpContext as you can always run into issues.
public class SessionSettings : ISessionSettings
{
private readonly HttpSessionStateBase _session;
public SessionSettings(HttpSessionStateBase session)
{
_session = session;
}
public T Get<T>(string key)
{
return (T)_session[key];
}
public void Set<T>(string key, T value)
{
_session[key] = value;
}
}
public interface ISessionSettings
{
T Get<T>(string key);
void Set<T>(string key, T value);
}
public class Sut
{
private ISessionSettings _sessionSettings;
public Sut(ISessionSettings sessionSettings)
{
_sessionSettings = sessionSettings;
}
public string GetCartId(HttpContextBase context)
{
if (_sessionSettings.Get<string>(CartSessionKey) == null)
{
if (!string.IsNullOrWhiteSpace(context.User.Identity.Name))
{
_sessionSettings.Set<string>(CartSessionKey, context.User.Identity.Name);
}
else
{
// Generate a new random GUID using System.Guid class
Guid tempCartId = Guid.NewGuid();
// Send tempCartId back to client as a cookie
_sessionSettings.Set<string>(CartSessionKey, tempCartId.ToString());
}
}
return _sessionSettings.Get<string>(CartSessionKey);
}
private string CartSessionKey = "key";
}
This way the code is more readable and easier to understand.

Setting HttpContext.Current.Session in a unit test

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