Unit test to mock ASP.NET Identity failed logon - c#

I'm writing a unit test using Moq and Microsoft's testing tools for an ASP.NET MVC 5 app that uses ASP.NET Identity. The test needs to simulate a logon failure in the Login POST action, such that it produces SignInStatus.Failure from the SignInManager.PasswordSignInAsync method of my AccountController:
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Login([Bind(Include = "Email,Password,RememberMe")]
LoginViewModel model, string returnUrl)
{
var result = await SignInManager.PasswordSignInAsync(model.Email, model.Password,
model.RememberMe, shouldLockout: true);
switch (result)
{
case SignInStatus.Success:
return Redirect(returnUrl);
case SignInStatus.LockedOut:
return View("Lockout");
case SignInStatus.RequiresVerification:
case SignInStatus.Failure:
default:
ModelState.AddModelError("", "Invalid login attempt.");
return View(model);
}
}
Here are the helper members/methods on my [TestClass]:
private Mock<HttpContextBase> _httpContextMock;
private Mock<HttpRequestBase> _httpRequestMock;
private Mock<HttpResponseBase> _httpResponseMock;
private MyApp.Controllers.AccountController _controller;
private Mock<ControllerContext> _controllerContextMock;
[TestInitialize]
public void SetupTests()
{
// Set up Moq
_httpContextMock = new Mock<HttpContextBase>();
_httpRequestMock = new Mock<HttpRequestBase>();
_httpResponseMock = new Mock<HttpResponseBase>();
_controller = new AccountController();
_controllerContextMock = new Mock<ControllerContext>();
}
private void SetContexts(IPrincipal user)
{
_httpContextMock.Setup(x => x.Items).Returns(new Dictionary<string, object>());
var sessionContainer = new HttpSessionStateContainer("id", new SessionStateItemCollection(),
new HttpStaticObjectsCollection(), 10, true,
HttpCookieMode.AutoDetect,
SessionStateMode.InProc, false);
//this adds aspnet session
_httpContextMock.Object.Items["AspSession"] = typeof(HttpSessionState).GetConstructor(
BindingFlags.NonPublic | BindingFlags.Instance,
null, CallingConventions.Standard,
new[] { typeof(HttpSessionStateContainer) },
null)
.Invoke(new object[] { sessionContainer });
_httpContextMock.Setup(x => x.User).Returns(user);
// https://stackoverflow.com/questions/674458/asp-net-mvc-unit-testing-controllers-that-use-urlhelper
_httpRequestMock.SetupGet(x => x.ApplicationPath).Returns("/");
_httpRequestMock.SetupGet(x => x.Url).Returns(new Uri("http://localhost/a", UriKind.Absolute));
_httpRequestMock.SetupGet(x => x.ServerVariables).Returns(new NameValueCollection());
_httpResponseMock.Setup(x => x.ApplyAppPathModifier(It.IsAny<string>())).Returns<string>(x => x);
var routes = new RouteCollection();
RouteConfig.RegisterRoutes(routes);
var helper = new UrlHelper(new RequestContext(_httpContextMock.Object, new RouteData()), routes);
_httpContextMock.SetupGet(x => x.Request).Returns(_httpRequestMock.Object);
_httpContextMock.SetupGet(x => x.Response).Returns(_httpResponseMock.Object);
// https://stackoverflow.com/questions/4066184/httpresponsebase-headers-are-empty-when-running-test
_httpResponseMock.Setup(r => r.OutputStream).Returns(new MemoryStream());
_httpResponseMock.Setup(r => r.Headers).Returns(new NameValueCollection());
var userStore = new Mock<IUserStore<ApplicationUser>>();
var userManager = new Mock<ApplicationUserManager>(userStore.Object);
var authenticationManager = new Mock<IAuthenticationManager>();
var signInManager = new Mock<ApplicationSignInManager>(userManager.Object, authenticationManager.Object);
var data = new Dictionary<string, object>()
{
{"a", "b"} // fake whatever you need here.
};
// https://stackoverflow.com/a/28574389/177416
_controller = new AccountController(
userManager.Object, signInManager.Object)
{
ControllerContext = new ControllerContext()
{
HttpContext = _httpContextMock.Object
}
};
_controller.ControllerContext.HttpContext.Items["owin.Environment"] = data;
_controller.Url = helper;
}
And here's my test method:
[TestMethod]
public async Task NoViewReturnedForFailedLogin()
{
// Arrange
var claimsIdentity = new ClaimsIdentity();
claimsIdentity.AddClaim(new Claim(ClaimTypes.Name, "fake#fake.org"));
var claimsPrincipal = new ClaimsPrincipal(claimsIdentity);
SetContexts(claimsPrincipal);
var vm = new LoginViewModel
{
Email = "faker#gmail.com",
Password = "Faker9999!",
RememberMe = false
};
_controllerContextMock.SetupGet(p => p.HttpContext.User.Identity.Name).Returns(vm.Email);
_controllerContextMock.SetupGet(p => p.HttpContext.Request.IsAuthenticated).Returns(false);
// Act
var result = await _controller.Login(vm, "http://www.google.com") as RedirectToRouteResult;
// Assert
// ? What do I assert?
}
As you can see, I have mock userStore, userManager, authenticationManager, and signInManager. If I step through the Login action, result always returns SignInStatus.Success.
Is there a way to have it fail? What should the test assert?

Related

How to write unit test for a MediatR handler C#

I'm trying to write unit test using Moq framework for one of my MediatR handler. I have the following code. Briefly, what this handler does is that it queries for the given key and returns a response that contains key and its value using EF Core. Also there is a basic cache mechanism. If the key found in cache it is pulled from cache and returned.
Handler
public GetConfigByKeyRequestHandler(MyContext context, ICacheProvider cacheProvider, IOptions<CacheConfigs> cacheConfigs)
{
this.context = context;
this.cacheProvider = cacheProvider;
this.cacheConfigs = cacheConfigs?.Value;
}
public async Task<ConfigResponse> Handle(GetConfigByKeyRequest request, CancellationToken cancellationToken)
{
ConfigResponse config;
if (!await cacheProvider.ExistsAsync(request.Key))
{
config = await context.Configs
.Where(x.ConfigKey.Equals(request.Key))
.Select(x =>
new ConfigResponse {
ConfigKey = x.ConfigKey,
ConfigValue = x.ConfigValue
})
.FirstOrDefaultAsync(cancellationToken);
if (config is not null)
{
await cacheProvider.PutAsync(new CacheItem<ConfigResponse>(request.Key, config), new CacheOptions
{
ExpireAfter = TimeSpan.FromMinutes(cacheConfigs.ExpireAfterInMinutes).TotalMilliseconds,
ExpireInactive = TimeSpan.FromMinutes(cacheConfigs.ExpireInActiveInMinutes).TotalMilliseconds
});
}
return config;
}
config = await cacheProvider.PullAsync<ConfigResponse>(request.Key);
return config;
}
I have thought that I should cover 2 different scenarios:
When the key found in the cache
When the key is not found in cache and it's returned from DbContext.
Unit tests
private Mock<ICacheProvider> cacheProviderMock;
private IOptions<CacheConfigs> cacheConfigs;
public GetConfigByKeyRequestHandlerTests()
{
cacheProviderMock = new Mock<ICacheProvider>();
cacheConfigs = Options.Create(
new CacheConfigs
{
ExpireAfterInMinutes = 3,
ExpireInActiveInMinutes = 3
});
}
[Fact]
public async Task GetConfigByKeyHandler_WhenKeyIsCached_ShouldReturnConfigByKey()
{
// arrange
var options = new DbContextOptionsBuilder<MyContext>().UseInMemoryDatabase("MyInMemoryDatabase").Options;
var configItems = Enumerable.Range(0, 5).Select(x => new Config
{
ConfigKey = $"key{x}",
ConfigValue = $"value{x}"
});
using (var context = new MyContext(options))
{
await context.Configs.AddRangeAsync(configItems);
await context.SaveChangesAsync();
}
using (var context = new MyContext(options))
{
cacheProviderMock.Setup(x => x.ExistsAsync(It.IsAny<string>())).Returns(Task.FromResult(true));
cacheProviderMock.Setup(x => x.PullAsync<ConfigResponse>("key2"))
.Returns(Task.FromResult(new ConfigResponse
{
ConfigKey = "key2",
ConfigValue = "value2"
}));
var getConfigByKeyHandler = new GetConfigByKeyRequestHandler(context, cacheProviderMock.Object, cacheConfigs);
var getConfigByKeyRequest = new GetConfigByKeyRequest("key2");
// act
var result = await getConfigByKeyHandler.Handle(getConfigByKeyRequest, CancellationToken.None);
// assert
Assert.NotNull(result);
Assert.Equal("key2", result.ConfigKey);
}
}
...
...
With the same logic, I have one more test for the other scenario that when key is not cached
...
...
[Fact]
public async Task GetConfigByKeyHandler_WhenKeyIsNotCached_ShouldReturnConfigByKey()
{
// arrange
var options = new DbContextOptionsBuilder<MyContext>().UseInMemoryDatabase("MyInMemoryDatabase").Options;
var configItems = Enumerable.Range(0, 5).Select(x => new Config
{
ConfigKey = $"key{x}",
ConfigValue = $"value{x}"
});
using (var context = new MyContext(options))
{
await context.Configs.AddRangeAsync(configItems);
await context.SaveChangesAsync();
}
using (var context = new MyContext(options))
{
cacheProviderMock.Setup(x => x.ExistsAsync(It.IsAny<string>())).Returns(Task.FromResult(false));
var getConfigByKeyHandler = new GetConfigByKeyRequestHandler(context, cacheProviderMock.Object, cacheConfigs);
var getConfigByKeyRequest = new GetConfigByKeyRequest("key2");
// act
var result = await getConfigByKeyHandler.Handle(getConfigByKeyRequest, CancellationToken.None);
// assert
Assert.NotNull(result);
Assert.Equal("key2", result.ConfigKey);
}
}
I have written 2 unit tests that covers the scenarios that I have mentioned above, but I'm not sure that they are reasonable ones and I'm not sure it should be tested like this way. Do you have any suggestions what/how should I write tests for the handler I shared above?

.Net Core Unit Testing With Url.Action

I am currently running unit tests on a controller that utilizes Url.Action to get the absolute path of two different routes. I am using Moq to set up the mock Action. The test is currently failing with the message 'Object reference not set to an instance of an object.'
[Edit - Clarifying the question]
When I debug the test, the Url shows to as a Mock<IUrlHelper> with an ActionContext property, but on the line where it calls the Url.Action, the Action property shows as null. What am I missing when setting up the Mock<IUrlHelper> that way the Url.Action doesn't come back as null?
I've checked multiple options for setting up Mock Url Actions, but nothing has seemed to work for me.
Here is the setup for the test
[Fact]
public async void SendGroup_PassesDetailsToNotificationService()
{
UrlActionContext actualContext = null;
var criteria = new NotificationCriteria { Section = 1, Subsection = 2 };
userSvc.GetNotificationGroupReturnValue = new List<NotificationRecipient>
{
new NotificationRecipient{ SmsAuth = true }
};
var actionContext = new ActionContext
{
ActionDescriptor = new ActionDescriptor(),
RouteData = new RouteData(),
};
var urlHelper = new Mock<IUrlHelper>();
urlHelper.SetupGet(h => h.ActionContext).Returns(actionContext);
urlHelper.Setup(h => h.Action(It.IsAny<UrlActionContext>()))
.Callback((UrlActionContext context) => actualContext = context);
controller.Url = urlHelper.Object;
var dummy = await controller.SendGroup(criteria);
Assert.Same(criteria, notificationSvc.SendGroupNotificationDetailUsed);
}
This is the code for the controller
[HttpPost]
public async Task<IActionResult> SendGroup([FromBody]NotificationCriteria criteria)
{
List<NotificationRecipient> recipients = (await userSvc.GetNotificationGroup(
criteria.Section, criteria.Subsection)).ToList();
if (!recipients.Any())
{
return BadRequest();
}
var uro = Url;
var sectionUrl = Url.Action("Index", "ReportHome", new {sectionId = criteria.Section}, Request.Scheme);
var profileUrl = Url.Action("Index", "Profile", null, Request.Scheme);
var detail = new NotificationDetail
{
SectionUrl = sectionUrl,
ProfileUrl = profileUrl,
Recipients = recipients
};
try
{
await notificationSvc.SendGroupNotification(detail);
}
catch
{
return StatusCode(500);
}
return Ok();
}

How to create request in Asp.net core 1 for testing

In my Asp.net core 1 app I have controller with following method:
[Microsoft.AspNetCore.Mvc.HttpPost()]
[Microsoft.AspNetCore.Mvc.RequireHttps]
public async System.Threading.Tasks.Task<Microsoft.AspNetCore.Mvc.IActionResult> Save()
{
if (ModelState.IsValid)
{
try
{
var pass = Request.Form["password"].ToString();
var pass1 = Request.Form["password1"].ToString();
if (!pass.Equals(pass1))
{
return View("~/Views/PasswordRecovery.cshtml");
}
}
catch (System.Exception ex)
{
return View("~/Views/Message.cshtml");
}
}
return View("~/Views/Message.cshtml");
}
I want to write a test for this method. So I have written this:
[Xunit.Fact]
public async System.Threading.Tasks.Task SavePassNotEqualTest()
{
var controller = new Controllers.PasswordRecoveryController(_mockRepo.Object);
var dic = new System.Collections.Generic.Dictionary<string, Microsoft.Extensions.Primitives.StringValues>();
dic.Add("password", "test");
dic.Add("password1", "test1");
var collection = new Microsoft.AspNetCore.Http.FormCollection(dic);
controller.Request.Form = collection; //request is null
var result = await controller.Save();
var viewResult = Xunit.Assert.IsType<Microsoft.AspNetCore.Mvc.ViewResult>(result);
Xunit.Assert.Equal("~/Views/Message.cshtml", viewResult.ViewName);
}
The problem is that I need to set some test values to Form, Form is in Request, and Request is NULL. I can not find, how can I create some not NULL request and fill it's Form with values.
EDIT
Answers helped me to finish up with following solution:
I've created a method that will return a FormCollection:
private Microsoft.AspNetCore.Http.FormCollection GetFormCollection()
{
var dic = new System.Collections.Generic.Dictionary<string, Microsoft.Extensions.Primitives.StringValues>();
dic.Add("password", "test");
dic.Add("password1", "test1");
return new Microsoft.AspNetCore.Http.FormCollection(dic);
}
And my test method is:
[Xunit.Fact]
public async System.Threading.Tasks.Task SavePassNotEqualTest()
{
var controller = new Findufix.Controllers.PasswordRecoveryController(_mockRepo.Object);
var httpContext = new Moq.Mock<Microsoft.AspNetCore.Http.HttpContext>();
httpContext.Setup( x => x.Request.Form).Returns(GetFormCollection());
controller.ControllerContext.HttpContext = httpContext.Object;
var result = await controller.Save();
var viewResult = Xunit.Assert.IsType<Microsoft.AspNetCore.Mvc.ViewResult>(result);
Xunit.Assert.Equal("~/Views/PasswordRecovery.cshtml", viewResult.ViewName);
}
If you pass a DefaultHttpContext to your controller, Request won't be null and you can assign the form to Request.Form. No mocking required.
[Xunit.Fact]
public async System.Threading.Tasks.Task SavePassNotEqualTest()
{
var controller = new Controllers.PasswordRecoveryController(_mockRepo.Object);
var dic = new System.Collections.Generic.Dictionary<string, Microsoft.Extensions.Primitives.StringValues>();
dic.Add("password", "test");
dic.Add("password1", "test1");
var collection = new Microsoft.AspNetCore.Http.FormCollection(dic);
// Give the controller an HttpContext.
controller.ControllerContext.HttpContext = new DefaultHttpContext();
// Request is not null anymore.
controller.Request.Form = collection;
var result = await controller.Save();
var viewResult = Xunit.Assert.IsType<Microsoft.AspNetCore.Mvc.ViewResult>(result);
Xunit.Assert.Equal("~/Views/Message.cshtml", viewResult.ViewName);
}
With Moq, you can do it like this:
var httpContext = new Mock<HttpContextBase>();
httpContext.Setup(c => c.Request.Form).Returns(delegate()
{
var formVaues = new NameValueCollection();
formVaues .Add("Id", "123");
formVaues .Add("Name", "Smith");
return formVaues ;
});
In Moq you can try to use Setup() or SetupGet() to teach it to return something that you need
something along the lines of
controller.SetupGet(x => x.Request.Form).Returns(collection);

Issue with writing a Unit Test for HttpContext.Current.Session

I have a working application for which I need to now add a full set of unit tests. The current code stores the user information as follows:
HttpContext.Current.Session["UserInfo"] = userData;
I'm using moq for my testing, and my unit test has the following code:
var server = new Mock<HttpServerUtilityBase>(MockBehavior.Loose);
var response = new Mock<HttpResponseBase>(MockBehavior.Strict);
var request = new Mock<HttpRequestBase>(MockBehavior.Strict);
var context = new Mock<HttpContextBase>();
context.SetupGet(x => x.Request).Returns(request.Object);
context.SetupGet(x => x.Response).Returns(response.Object);
context.SetupGet(x => x.Server).Returns(server.Object);
var controller = new LoginController();
controller.ControllerContext = new ControllerContext(
context.Object,
new RouteData(),
controller);
JsonResult result = controller.LoginUser(
new LoginHelper {
userName = "myusername",
password = "invalidpassword"
}
) as JsonResult;
Of course when I get to where the login process tries to create the session data, I get an "Object reference not set to an instance of an object." error becuase HttpContext.Current is null.
Some research has shown me that using Current isn't exactly compatible with MSTest, so I understand that I might need to change the way I store/load my user information. However, I would like some advice on which way to go here.
I would appreciate any suggestions on either how to get my unit tests to work, or a different method to store the user info to make it more compatible with unit tests.
This was the solution I came up with. It is a combination of a few things I found in various places including here:
First, I created 2 classes .. MockHttpSession, and MockHelpers
public class MockHttpSession : HttpSessionStateBase
{
Dictionary<string, object> m_SessionStorage = new Dictionary<string, object>();
public override object this[string name]
{
get { return m_SessionStorage[name]; }
set { m_SessionStorage[name] = value; }
}
public override void Abandon()
{
// Do nothing
}
}
public class MockHelpers
{
public static HttpContext FakeHttpContext()
{
var httpRequest = new HttpRequest("", "http://localhost/", "");
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;
}
}
Then I changed my unit test code to use these:
var server = new Mock<HttpServerUtilityBase>(MockBehavior.Loose);
var response = new Mock<HttpResponseBase>(MockBehavior.Strict);
var request = new Mock<HttpRequestBase>(MockBehavior.Strict);
var session = new MockHttpSession();
var context = new Mock<HttpContextBase>();
context.SetupGet(x => x.Request).Returns(request.Object);
context.SetupGet(x => x.Response).Returns(response.Object);
context.SetupGet(x => x.Server).Returns(server.Object);
context.SetupGet(x => x.Session).Returns(session);
HttpContext.Current = MockHelpers.FakeHttpContext();
var controller = new LoginController();
controller.ControllerContext = new ControllerContext(context.Object, new RouteData(), controller);
JsonResult result = controller.LoginUser(new LoginHelper { userName = "MyUserName", password = ""InvalidPassword }) as JsonResult;
This was able to successfully test the code with both valid and invalid passwords.
Thanks everyone for your help

How can I get Session.SessionID from my MVC-Framework in unit tests?

For my unit-tests I use Microsoft.VisualStudio.TestTools.UnitTesting and MvcContrib.TestHelper
my action in controller:
public ActionResult index()
{
try
{
Session.Add("username", "Simon");
var lSessionID = Session.SessionID;
return Content(lSessionID);
}
catch
{
}
return Content("false");
}
my unit-test:
[TestMethod]
public void IndexTestMethod1()
{
TestControllerBuilder builder = new TestControllerBuilder();
StartController controller = new StartController();
builder.InitializeController(controller);
var lResult = controller.index();
var lReturn = ((System.Web.Mvc.ContentResult)(lResult)).Content; // returns "false"
Assert.IsFalse(lReturn == "false");
}
When I call the index()-action in my browser, it shows the Session-ID. When I call the action via my unit-test, the lReturn is "false" and not the expected Session-ID.
How can I get the Session.SessionID in my unit-test?
The Session variable is read from ControllerContext.HttpContext.Session , and Session is of the type HttpSessionStateBase.
with in the unit test, you can use set the ControllerContext object. ( or use any mock providers like moq). I have not tested the code
var contextMock = new Mock<ControllerContext>();
var mockHttpContext = new Mock<HttpContextBase>();
var session = new Mock<HttpSessionStateBase>();
mockHttpContext.Setup(h => h.Session).Returns(session.Object);
contextMock.Setup(c => c.HttpContext).Returns(mockHttpContext.Object);

Categories