Reputation: 57
I've just read more than one and I don't find any solution for my problem. I've create WebApi + MVC and Unit Test project with FrameWork 4.5. I have this HomeController.cs with this method:
using Net.Personal.Authentication.FormAuthentication;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Principal;
using System.Web;
using System.Web.Mvc;
namespace FormAuthenticationProva.Controllers
{
public class HomeController : Controller
{
public ActionResult Index()
{
ViewBag.Title = "Home Page";
var guidAccount = "xxxxxxxx-xxxx-xxxx-xxxx-422632e0bd95";
var userData = new CookieUserData(guidAccount) { GuidAccount = guidAccount };
HttpContextBase httpContextBase = this.HttpContext;
AuthenticationProvider _authProvider = new AuthenticationProvider(httpContextBase.ApplicationInstance.Context);
_authProvider.CheckAuthorizationForUrl("http://pippo");
return View();
}
}
}
And the HomeControllerTest.cs with this code:
using System.Web.Mvc;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using FormAuthenticationProva.Controllers;
using Moq;
using System.Web;
using System.Collections.Specialized;
using System.Web.Routing;
namespace FormAuthenticationProva.Tests.Controllers
{
[TestClass]
public class HomeControllerTest
{
[TestMethod]
public void Index()
{ // Disposizione
var formData = new NameValueCollection { { "id", "test" } };
var request = new Mock<HttpRequestBase>();
request.SetupGet(r => r.Form).Returns(formData);
var context = new Mock<HttpContextBase>();
context.SetupGet(c => c.Request).Returns(request.Object);
var controller = new HomeController();
controller.ControllerContext = new ControllerContext(context.Object, new RouteData(), controller);
// Azione
ViewResult result = controller.Index() as ViewResult;
// Asserzione
Assert.IsNotNull(result);
Assert.AreEqual("Home Page", result.ViewBag.Title);
}
}
}
And the AuthenticationProvider.cs class is here:
/* AuthenticationProvider.cs code */
using System;
using System.Security.Principal;
using System.Threading;
using System.Web;
using System.Web.Script.Serialization;
using System.Web.Security;
namespace Net.Personal.Authentication.FormAuthentication
{
public class AuthenticationProvider : IFormsAuthentication
{
public AuthContextConfiguration AuthContextConfiguration { get; set; }
public AuthenticationProvider() {}
public AuthenticationProvider(HttpContext context , AuthContextConfiguration authContextConfiguration = null)
{
AuthContextConfiguration = AuthContextConfiguration ?? new AuthContextConfiguration(context);
}
private void SetPrincipal(IPrincipal principal)
{
Thread.CurrentPrincipal = principal;
if (HttpContext.Current != null)
{
HttpContext.Current.User = principal;
}
}
public void SignIn(string userName, bool createPersistentCookie)
{
FormsAuthentication.SetAuthCookie(userName, createPersistentCookie);
}
public void SignIn(string userName, bool createPersistentCookie, ICookieUserData userData)
{
this.SetAuthCookie<ICookieUserData>(userName, true, userData);
HttpContext.Current.Cache.Insert(userName, userData);
}
public int SetAuthCookie<T>( string name, bool rememberMe, T userData)
{
/// In order to pickup the settings from config, we create a default cookie and use its values to create a
/// new one.
if (string.IsNullOrEmpty(((ICookieUserData)userData).Name)) ((ICookieUserData)userData).Name = name;
var cookie = FormsAuthentication.GetAuthCookie(name, rememberMe);
var ticket = FormsAuthentication.Decrypt(cookie.Value);
var newTicket = new FormsAuthenticationTicket(ticket.Version, ticket.Name, ticket.IssueDate, ticket.Expiration,
ticket.IsPersistent, new JavaScriptSerializer().Serialize(userData), ticket.CookiePath);
var encTicket = FormsAuthentication.Encrypt(newTicket);
/// Use existing cookie. Could create new one but would have to copy settings over...
cookie.Value = encTicket;
HttpContext.Current.Response.Cookies.Add(cookie);
return encTicket.Length;
}
public void SignOut()
{
FormsAuthentication.SignOut();
}
public bool IsAuthorized()
{
return HttpContext.Current.User != null &&
HttpContext.Current.User.Identity != null &&
HttpContext.Current.User.Identity.IsAuthenticated;
}
public void SetUserOnApplication_AuthenticateRequest<T>(HttpContext context)
{
PrincipalUser principal = null;
ICookieUserData userData = null;
// Extract the forms authentication cookie
string cookieName = FormsAuthentication.FormsCookieName;
HttpCookie authCookie = context.Request.Cookies[cookieName];
if (null == authCookie)
{
// There is no authentication cookie.
return;
}
var nameIdentity = HttpContext.Current.User.Identity.Name;
if(string.IsNullOrEmpty(nameIdentity)) {
return;
}
if(HttpContext.Current.Cache[nameIdentity] ==null) {
FormsAuthenticationTicket authTicket = null;
try {
authTicket = FormsAuthentication.Decrypt(authCookie.Value);
}
catch (Exception ex) {
// Log exception details (omitted for simplicity)
return;
}
if (null == authTicket) {
// Cookie failed to decrypt.
return;
}
userData = (ICookieUserData)new JavaScriptSerializer().Deserialize<T>(authTicket.UserData);
// When the ticket was created, the UserData property was assigned a
// pipe delimited string of role names.
string[] roles = authTicket.UserData.Split('|');
// Create an Identity object
//FormsIdentity id = new FormsIdentity(authTicket);
// This principal will flow throughout the request.
//PrincipalUser principal = new PrincipalUser(id, roles);
} else {
userData = (ICookieUserData)HttpContext.Current.Cache[nameIdentity];
}
principal = new PrincipalUser(userData);
// Attach the new principal object to the current HttpContext object
context.User = principal;
}
public void CheckAuthorization()
{
if (!this.IsAuthorized()) throw new Exception("Access not allowed");
}
public void CheckAuthorizationForUrl(string url)
{
AuthContextConfiguration.CheckAuthorizationForUrl(url);
if (AuthContextConfiguration.CheckRequiredAuth() && !this.IsAuthorized()) throw new Exception("Access not allowed");
}
}
public class PrincipalUser : IPrincipal
{
private ICookieUserData _userData;
private GenericIdentity _identity;
public PrincipalUser(ICookieUserData userData)
{
_identity = new GenericIdentity(userData.Name);
_userData = userData;
}
public IIdentity Identity
{
get
{
return _identity;
}
}
public ICookieUserData UserData
{
get
{
return _userData;
}
}
public bool IsInRole(string role)
{
return _userData.Role.Contains(role);
}
}
public interface ICookieUserData
{
string Name { get; set; }
string Role { get; set; }
}
}
And now the problem is when I debug from [TestMethod] and I go into method with debugging (F10) and putted breakpoint in the method in HomeController.cs I see this System.Web.HttpContext.Current is every time null! What's the problem? I use Moq.
Upvotes: 3
Views: 13183
Reputation: 2176
I will scope my answer only to System.Web.HttpContext.Current
.
It is set automatically by IIS.
In order to use it you could use Microsoft fakes (https://learn.microsoft.com/en-us/visualstudio/test/isolating-code-under-test-with-microsoft-fakes).
The article above explains how to implement it. You would then have access to Current without getting null error.
Upvotes: 1
Reputation: 247018
System.Web.HttpContext.Current
is populated by IIS which is not present/active during unit testing. Hence null. Do not tightly couple your code to HttpContext
for that very reason. Instead encapsulate that behind abstractions that can be mocked when testing in isolation.
Bad design aside, for your specific design you are trying to access a static dependency external to the controller. If faking the controller's context as demonstrated in your current test, then access the controller's context instead of calling the static context.
//... code removed for brevity
var _authProvider = new AuthenticationProvider(this.HttpContext);
//... code removed for brevity
With that out of the way, the design of the controller should be refactored to explicitly depend on abstractions instead of concretions.
for example, here is an abstraction for the provider
public interface IAuthenticationProvider : IFormsAuthentication {
void CheckAuthorizationForUrl(string url);
//...other members
}
public class AuthenticationProvider : IAuthenticationProvider {
//...
}
The controller should explicitly depend on this via constructor injection
public class HomeController : Controller {
private readonly IAuthenticationProvider authProvider;
public HomeController(IAuthenticationProvider authProvider) {
this.authProvider = authProvider;
}
public ActionResult Index() {
ViewBag.Title = "Home Page";
var guidAccount = "xxxxxxxx-xxxx-xxxx-xxxx-422632e0bd95";
var userData = new CookieUserData(guidAccount) { GuidAccount = guidAccount };
authProvider.CheckAuthorizationForUrl("http://pippo");
return View();
}
}
The IAuthenticationProvider
implementation should be configured to be injected into the controller at run time using the framework's DependencyResolver
if using dependency injection, but can now be replaced when testing the controller in isolation so as not to be coupled to framework implementation concerns.
[TestClass]
public class HomeControllerTest {
[TestMethod]
public void Index(){
// Disposizione
var authMock = new Mock<IAuthenticationProvider>();
var controller = new HomeController(authMock.Object);
// Azione
ViewResult result = controller.Index() as ViewResult;
// Asserzione
Assert.IsNotNull(result);
Assert.AreEqual("Home Page", result.ViewBag.Title);
}
}
Upvotes: 4