I am writing unit tests for a project in ASP.NET MVC 1.0 using Moq and MvcContrib TestHelper classes. I have run into a problem.
When I come to Roles.AddUserToRole in my AccountController, I get a System.NotSupportedException. The Roles class is static and Moq cannot mock a static class.
What can I do?
You could use a pattern like DI (Dependency Injection). In your case, I would pass a RoleProvider to the AccountController, which would be the default RoleProvider by default, and a mock object in your tests. Something like:
public class AccountController
{
private MembershipProvider _provider;
private RoleProvider roleProvider;
public AccountController()
: this(null, null)
{
}
public AccountController(MembershipProvider provider, RoleProvider roleProvider)
{
_provider = provider ?? Membership.Provider;
this.roleProvider = roleProvider ?? System.Web.Security.Roles.Provider;
}
}
The MVC runtime will call the default constructor, which in turn will initialize the AccountController with the default role provider. In your unit test, you can directly call the overloaded constructor, and pass a MockRoleProvider (or use Moq to create it for you):
[Test]
public void AccountControllerTest()
{
AccountController controller = new AccountController(new MockMembershipProvider(), new MockRoleProvider());
}
EDIT: And here's how I mocked the entire HttpContext, including the principal user.
To get a Moq version of the HttpContext:
public static HttpContextBase GetHttpContext(IPrincipal principal)
{
var httpContext = 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 = principal;
httpContext.Setup(ctx => ctx.Request).Returns(request.Object);
httpContext.Setup(ctx => ctx.Response).Returns(response.Object);
httpContext.Setup(ctx => ctx.Session).Returns(session.Object);
httpContext.Setup(ctx => ctx.Server).Returns(server.Object);
httpContext.Setup(ctx => ctx.User).Returns(user);
return httpContext.Object;
}
A mock implementation of Principal:
public class MockPrincipal : IPrincipal
{
private IIdentity _identity;
private readonly string[] _roles;
public MockPrincipal(IIdentity identity, string[] roles)
{
_identity = identity;
_roles = roles;
}
public IIdentity Identity
{
get { return _identity; }
set { this._identity = value; }
}
public bool IsInRole(string role)
{
if (_roles == null)
return false;
return _roles.Contains(role);
}
}
A MockIdentity:
public class MockIdentity : IIdentity
{
private readonly string _name;
public MockIdentity(string userName) {
_name = userName;
}
public override string AuthenticationType
{
get { throw new System.NotImplementedException(); }
}
public override bool IsAuthenticated
{
get { return !String.IsNullOrEmpty(_name); }
}
public override string Name
{
get { return _name; }
}
}
And the magic call:
MockIdentity identity = new MockIdentity("JohnDoe");
var httpContext = MoqHelpers.GetHttpContext(new MockPrincipal(identity, null));
Note that I edited the code above to leave out some custom stuff, but I'm quite sure this should still work.
Now I run into another problem when I try to test the ChangePassword() method in ASP.NET MVC.
try
{
if (MembershipService.ChangePassword(User.Identity.Name, currentPassword, newPassword))
{
if (!TempData.ContainsKey("ChangePassword_success"))
{
TempData.Add("ChangePassword_success", true);
}
return PartialView("ChangePassword");
}
Now I get that User is null, when I reach this line. In my testclass I have:
mockMembershipService.Setup(cp => cp.ChangePassword("johndoe", currentPassword, newPassword)).Returns(true);
I thought that this would work, but it doesn't care for that I send "johndoe". And If I were to mock IPrincipal, the User property is readonly.
TypeMock Isolator does mocking of statics etc. But I second (and +1'd) Razzie's answer.
I have done what you coded, but I still get that User is null when it reaches:
mockMembershipService.Setup(cp => cp.ChangePassword("johndoe", currentPassword, newPassword)).Returns(true);
In my Testclass I have:
//Arrange (Set up a scenario)
var mockMembershipService = new Mock<IMembershipService>();
MockIdentity identity = new MockIdentity("JohnDoe");
var httpContext = MoqHelpers.GetHttpContext(new MockPrincipal(identity, null));
var controller = new AccountController(null, mockMembershipService.Object, null, null, null);
string currentPassword = "qwerty";
string newPassword = "123456";
string confirmPassword = "123456";
// Expectations
mockMembershipService.Setup(pw => pw.MinPasswordLength).Returns(6);
mockMembershipService.Setup(cp => cp.ChangePassword("johndoe", currentPassword, newPassword)).Returns(true);
Do I call my cp.ChangePassword with wrong parameters? And should MVCContrib Testhelpers classes be able to mock Http context and so on? I just can't find info for how to setup User.Identity.Name with MVCContrib.
I have used this from a tutorial to test something (mock) session:
var builder = new TestControllerBuilder();
var controller = new AccountController(mockFormsAuthentication.Object, mockMembershipService.Object, mockUserRepository.Object, null, mockBandRepository.Object);
builder.InitializeController(controller);
EDIT: I have come a little further:
MockIdentity identity = new MockIdentity("JohnDoe");
var httpContext = MoqHelpers.GetHttpContext(new MockPrincipal(identity, null));
var controller = new AccountController(null, mockMembershipService.Object, null, null, null);
controller.ControllerContext = new ControllerContext(httpContext, new RouteData(), controller);
but my now I can't get my cp.ChangePassword in the expectation to return true:
mockMembershipService.Setup(cp => cp.ChangePassword("johndoe", currentPassword, newPassword)).Returns(true);
I am sending "johndoe" string, because, it requires a string as a parameter for User.Identity.Name, but it doesn't return true.
Related
I get this exception from time to time :
The 'Email' property on 'User' could not be set to a 'System.Int64' value. You must set this property to a non-null value of type 'System.String'. Method Message:, LogException: System.InvalidOperationException: The 'Email' property on 'User' could not be set to a 'System.Int64' value. You must set this property to a non-null value of type 'System.String'.
at System.Data.Entity.Core.Common.Internal.Materialization.Shaper.ErrorHandlingValueReader1.GetValue(DbDataReader reader, Int32 ordinal)
at lambda_method(Closure , Shaper )
at System.Data.Entity.Core.Common.Internal.Materialization.Shaper.HandleEntityAppendOnly[TEntity](Func2 constructEntityDelegate, EntityKey entityKey, EntitySet entitySet)
at lambda_method(Closure , Shaper )
at System.Data.Entity.Core.Common.Internal.Materialization.Coordinator1.ReadNextElement(Shaper shaper)
at System.Data.Entity.Core.Common.Internal.Materialization.Shaper1.SimpleEnumerator.MoveNext()
at System.Linq.Enumerable.FirstOrDefault[TSource](IEnumerable`1 source)
at Project.Services.UserService.FindById(Int64 userId)
I'm using Asp.net Identity in MVC project.
My User class like :
public class User : IdentityUser<long, IdentityConfig.UserLogin, IdentityConfig.UserRole, IdentityConfig.UserClaim>
{
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(IdentityConfig.CustomUserManager manager)
{
// Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
// Add custom user claims here
return userIdentity;
}
[MaxLength(256)]
[Index(IsUnique = true)]
[Required]
public override string Email { get; set; }
[Required]
[MaxLength(256)]
public string FirstName { get; set; }
// rest of properties
....
}
UserManager :
public class CustomUserManager : UserManager<User, long>
{
public CustomUserManager(IUserStore<User, long> store, IdentityFactoryOptions<CustomUserManager> options) : base(store)
{
this.UserValidator = new UserValidator<User, long>(this)
{
AllowOnlyAlphanumericUserNames = false,
RequireUniqueEmail = true
};
// Configure validation logic for passwords
PasswordValidator = new PasswordValidator
{
RequiredLength = 8,
RequireLowercase = true,
RequireUppercase = true,
RequireDigit = true
};
// Configure user lockout defaults
UserLockoutEnabledByDefault = true;
DefaultAccountLockoutTimeSpan = TimeSpan.FromMinutes(5);
MaxFailedAccessAttemptsBeforeLockout = 5;
// Register two factor authentication providers. This application uses Phone and Emails as a step of receiving a code for verifying the user
// You can write your own provider and plug it in here.
RegisterTwoFactorProvider("Google Authentication", new GoogleAuthenticatorTokenProvider());
var provider = new MachineKeyProtectionProvider();
UserTokenProvider = new DataProtectorTokenProvider<User,long>(provider.Create("ResetPasswordPurpose"));
}
}
UserService:
public class UserService : EntityService<User>, IUserService
{
private readonly IdentityConfig.CustomUserManager _userManager;
public UserService(MyDbContext context, IdentityConfig.CustomUserManager userManager) : base(context)
{
_userManager = userManager;
}
public User FindById(long userId)
{
return _userManager.Users.FirstOrDefault(x => x.Id == userId);
}
// other methods..
}
Register Autofac:
builder.RegisterModule(new ServiceModule());
builder.RegisterModule(new EfModule());
builder.RegisterType<IdentityConfig.RoleStore>().As<IRoleStore<IdentityConfig.Role, long>>().InstancePerRequest();
builder.RegisterType<IdentityConfig.CustomUserStore>().As<IUserStore<User, long>>().InstancePerRequest();
builder.RegisterType<IdentityConfig.CustomUserManager>().AsSelf().InstancePerRequest();
builder.RegisterType<IdentityConfig.CustomSignInManager>().AsSelf().InstancePerRequest();
builder.RegisterType<IdentityConfig.CustomRoleManager>().AsSelf().InstancePerRequest();
builder.Register<IAuthenticationManager>(c => HttpContext.Current.GetOwinContext().Authentication);
builder.Register(c => new IdentityFactoryOptions<IdentityConfig.CustomUserManager>
{
DataProtectionProvider = new DpapiDataProtectionProvider("MyWebAppName"),
Provider = new IdentityFactoryProvider<IdentityConfig.CustomUserManager>()
}).InstancePerRequest();
public class ServiceModule : Module
{
protected override void Load(ContainerBuilder builder)
{
builder.RegisterAssemblyTypes(Assembly.Load("Project.Services"))
.Where(t => t.Name.EndsWith("Service") || t.Name.EndsWith("Validator"))
.AsImplementedInterfaces()
.InstancePerLifetimeScope();
}
}
public class EfModule : Module
{
protected override void Load(ContainerBuilder builder)
{
builder.RegisterType(typeof(MyDbContext)).AsSelf().WithParameter("connectionString", ConfigurationManager.ConnectionStrings["DefaultConnection"].ConnectionString).InstancePerRequest();
}
}
What I noticed also is this error affect some of other entities not just the user !
The problem is the application runs for some time and then gives this kind of errors too much, which does not make any sense to me and makes me mad.
I'm using Azure SQL , Azure web services, Autofac.
Same issue here. It happens on medium to high demand. I don't know what to do anymore. I'm recycling 5x/day.
I couldn't find any standard. The exception throws in many different methods. No standard. Looks completely random.
It seems that i'm trying to retrieve an information on DB and it always returns blank data, so an error is thrown when it tries to cast the null data to the model.
I have an MVC app which I'm having trouble ex[posing a custom principal to my views. It has the following classes that help me manage auth cookies.
public class AuthenticationManager
{
public void SetAuthCookie(UserViewModel user)
{
var serializeModel = new CustomPrincipalSerializeModel
{
Id = user.UserId,
Email = user.Email,
Name = user.Name
};
var serializer = new JavaScriptSerializer();
var customPrincipal = customPrincipalMapper.Convert(serializeModel);
var httpContext = ContextHelper.GetHttpContextBase();
httpContext.User = customPrincipal;
var userData = serializer.Serialize(serializeModel);
var authTicket = new FormsAuthenticationTicket(1, serializeModel.Email, DateTime.Now, DateTime.Now.AddYears(5), false, userData);
var encTicket = FormsAuthentication.Encrypt(authTicket);
var authCookie = new HttpCookie(FormsAuthentication.FormsCookieName, encTicket);
httpContext.Response.Cookies.Add(authCookie);
}
}
public static class ContextHelper
{
public static HttpContextBase GetHttpContextBase()
{
return new HttpContextWrapper(HttpContext.Current);
}
}
I also have the following BaseViewPage classes which allow me to expose the current user to my views:
public abstract class BaseViewPage : WebViewPage
{
public virtual new CustomPrincipal User
{
get { return base.User as CustomPrincipal; }
}
}
public abstract class BaseViewPage<TModel> : WebViewPage<TModel>
{
public virtual new CustomPrincipal User
{
get { return base.User as CustomPrincipal; }
}
}
FWIW, this requires <pages pageBaseType="Giftster.Web.Views.BaseViewPage"> to be in my View's Web.config file.
Immediately, after the httpContext.User = customPrincipal; line runs in AuthenticationManager.SetAuthCookie(), the type of object which ContextHelper.GetHttpContextBase().User returns is a CustomPrincipal. If I refresh the page, however, and put a break point in BaseViewPage, base.User as CustomPrincipal (and ContextHelper.GetHttpContextBase().User as CustomPrincipal for that matter) equals null. base.User is not null, though: It is of type GenericPrincipal, so there is either a casting issue or a problem with storing/retrieving the correct type.
Why is base.User in my BaseViewPage not of type CustomPrincipal?
Thanks in advance.
You need to create your CustomPrincipal from the cookie in each request and add it to the current context. Add the following to the Global.asax.cs file
protected void Application_PostAuthenticateRequest(object sender, EventArgs e)
{
// Get the authentication cookie
HttpCookie authCookie = Request.Cookies[FormsAuthentication.FormsCookieName];
// If the cookie can't be found, don't issue the ticket
if (authCookie == null)
{
return;
}
// Extract the forms authentication cookie
FormsAuthenticationTicket authTicket = FormsAuthentication.Decrypt(authCookie.Value);
// Deserialise user data
JavaScriptSerializer serializer = new JavaScriptSerializer();
CustomPrincipalSerializeModel data = serializer.Deserialize<CustomPrincipalSerializeModel>(authTicket.UserData);
// Create principal
CustomPrincipal principal = new CustomPrincipal(authTicket.Name);
// Set the properties of CustomPrincipal
principal.Email = data.Email;
.... // etc
// Assign to current context
HttpContext.Current.User = principal;
}
Note also the following line is not required in you SetAuthCookie() method
httpContext.User = customPrincipal;
You might also consider adding the following to a BaseController (from which all other controllers derive) so the CustomPrincipal properties can be accessed easily in each controller method
public new CustomPrincipalSerializeModel User
{
get { return (CustomPrincipal)HttpContext.User; }
}
I am trying to test my Account controller by using Moq here is what i have done
Controller
private readonly IWebSecurity _webSecurity;
public AccountController(IWebSecurity webSecurity)
{
this._webSecurity = webSecurity;
}
public ActionResult Login(LoginModel model, string returnUrl)
{
if (ModelState.IsValid && _webSecurity.login(model))
{
return RedirectToLocal(returnUrl);
}
// If we got this far, something failed, redisplay form
ModelState.AddModelError("", "The user name or password provided is incorrect.");
return View(model);
}
private ActionResult RedirectToLocal(string returnUrl)
{
if (Url.IsLocalUrl(returnUrl))
{
return Redirect(returnUrl);
}
else
{
return RedirectToAction("Index", "Home");
}
}
IWebSecurity
public interface IWebSecurity
{
bool login(LoginModel model);
}
public class WebSecurity : IWebSecurity
{
public bool login(LoginModel model)
{
return WebMatrix.WebData.WebSecurity.Login(model.UserName, model.Password, model.RememberMe);
}
}
MyTestClass
[AfterScenario]
public void OnAfterScenario() {
mockRepository.VerifyAll();
}
LoginModel loginModel;
AccountController _controller;
#region Initializing Mock Repository
readonly Mock<IWebSecurity> mockRepository = new Mock<IWebSecurity>(MockBehavior.Loose);
ViewResult viewResult;
#endregion
[Given]
public void Given_Account_controller()
{
_controller = new AccountController(mockRepository.Object);
}
[When]
public void When_login_is_called_with_LoginModel(Table table)
{
loginModel = new LoginModel
{
UserName = table.Rows[0][1],
Password = table.Rows[1][1]
};
mockRepository.Setup(x => x.login(loginModel)).Returns(true);
viewResult = (ViewResult)_controller.Login(loginModel, "/");
}
[Then]
public void Then_it_should_validate_LoginModel()
{
Assert.IsTrue(_controller.ModelState.IsValid);
}
[Then]
public void Then_it_should_return_default_view()
{
Assert.AreEqual(viewResult.ViewName, "Index");
}
But my test is failing and its giving expection when if come to Url.IsLocal in Redirect to Local method . so i think here is should mock my httpcontextbase and httpcontextrequestbase .
But don't know how to mock that .
Thanks in advance
You should mock the HttpContext. I wrote this helper to do this kind of things
public static Mock<HttpContextBase> MockControllerContext(bool authenticated, bool isAjaxRequest)
{
var request = new Mock<HttpRequestBase>();
request.SetupGet(r => r.HttpMethod).Returns("GET");
request.SetupGet(r => r.IsAuthenticated).Returns(authenticated);
request.SetupGet(r => r.ApplicationPath).Returns("/");
request.SetupGet(r => r.ServerVariables).Returns((NameValueCollection)null);
request.SetupGet(r => r.Url).Returns(new Uri("http://localhost/app", UriKind.Absolute));
if (isAjaxRequest)
request.SetupGet(x => x.Headers).Returns(new System.Net.WebHeaderCollection { { "X-Requested-With", "XMLHttpRequest" } });
var server = new Mock<HttpServerUtilityBase>();
server.Setup(x => x.MapPath(It.IsAny<string>())).Returns(BasePath);
var response = new Mock<HttpResponseBase>();
response.Setup(r => r.ApplyAppPathModifier(It.IsAny<string>())).Returns((String url) => url);
var session = new MockHttpSession();
var mockHttpContext = new Mock<HttpContextBase>();
mockHttpContext.Setup(c => c.Request).Returns(request.Object);
mockHttpContext.Setup(c => c.Response).Returns(response.Object);
mockHttpContext.Setup(c => c.Server).Returns(server.Object);
mockHttpContext.Setup(x => x.Session).Returns(session);
return mockHttpContext;
}
public class MockHttpSession : HttpSessionStateBase
{
private readonly Dictionary<string, object> sessionStorage = new Dictionary<string, object>();
public override object this[string name]
{
get { return sessionStorage.ContainsKey(name) ? sessionStorage[name] : null; }
set { sessionStorage[name] = value; }
}
public override void Remove(string name)
{
sessionStorage.Remove(name);
}
}
and in a test method you use it like that
private AccountController GetController(bool authenticated)
{
var requestContext = new RequestContext(Evoltel.BeniRosa.Web.Frontend.Tests.Utilities.MockControllerContext(authenticated, false).Object, new RouteData());
var controller = new CofaniController(cofaniRepository.Object, categorieRepository.Object, emailService.Object, usersService.Object)
{
Url = new UrlHelper(requestContext)
};
controller.ControllerContext = new ControllerContext()
{
Controller = controller,
RequestContext = requestContext
};
return controller;
}
[Test]
public void LogOn_Post_ReturnsRedirectOnSuccess_WithoutReturnUrl()
{
AccountController controller = GetController(false);
var httpContext = Utilities.MockControllerContext(false, false).Object;
controller.ControllerContext = new ControllerContext(httpContext, new RouteData(), controller);
LogOnModel model = new LogOnModel()
{
UserName = "someUser",
Password = "goodPassword",
RememberMe = false
};
ActionResult result = controller.LogOn(model, null);
Assert.IsInstanceOf(typeof(RedirectToRouteResult), result);
RedirectToRouteResult redirectResult = (RedirectToRouteResult)result;
Assert.AreEqual("Home", redirectResult.RouteValues["controller"]);
Assert.AreEqual("Index", redirectResult.RouteValues["action"]);
}
Hope it helps
In this particular issue you can simply overwrite controller's Url property with mocked UrlHelper class.
For HttpContext mocking, it might be good to inject HttpContextBase to your controller and configure your DI container to serve the proper one for you. It would ease mocking it later for testing purposes. I believe Autofac has some automatic way to configure container for ASP.NET-related classes like HttpContextBase.
EDIT
It seems you can't mock UrlHelper with Moq, as #lazyberezovsky wrote - you can mock only interfaces and virtual methods. But it does not stop you from writing your own mocked object. That's true you need to mock HttpContext, as it's required by UrlHelper constructor (actually, it's required by RequestContext constructor, which is required by UrlHelper constructor)... Moreover, IsLocalUrl does not use anything from context, so you do not have to provide any additional setup.
Sample code would look like that:
Controller:
public ActionResult Foo(string url)
{
if (Url.IsLocalUrl(url))
{
return Redirect(url);
}
return RedirectToAction("Index", "Home");
}
Tests:
[TestClass]
public class HomeControllerTests
{
private Mock<HttpContextBase> _contextMock;
private UrlHelper _urlHelperMock;
public HomeControllerTests()
{
_contextMock = new Mock<HttpContextBase>();
_urlHelperMock = new UrlHelper(new RequestContext(_contextMock.Object, new RouteData()));
}
[TestMethod]
public void LocalUrlTest()
{
HomeController controller = new HomeController();
controller.Url = _urlHelperMock;
RedirectResult result = (RedirectResult)controller.Foo("/");
Assert.AreEqual("/", result.Url);
}
[TestMethod]
public void ExternalUrlTest()
{
HomeController controller = new HomeController();
controller.Url = _urlHelperMock;
RedirectToRouteResult result = (RedirectToRouteResult)controller.Foo("http://test.com/SomeUrl");
Assert.AreEqual("Index", result.RouteValues["action"]);
Assert.AreEqual("Home", result.RouteValues["controller"]);
}
}
I'm trying to take advantage of the recent ControllerContext refactoring in asp.net mvc rc1. I should be able to stub the session rather simply but I keep getting a System.NullReferenceException on line 2 when running the following code:
var mockContext = MockRepository.GenerateStub<ControllerContext>();
mockContext.Stub(x => x.HttpContext.Session["MyKey"]).Return("MyValue");
What am I doing wrong?
Edit: I just verified I have the latest version of rhino as of this post.
You need to mock HttpContext too to make this working.
I'm using a mock of HttpContext for this:
public class HttpContextMock
{
private readonly HttpContextBase _contextBase;
private readonly HttpRequestBase _requestBase;
private readonly HttpResponseBase _responseBase;
private readonly HttpSessionStateBase _sessionStateBase;
private readonly HttpServerUtilityBase _serverUtilityBase;
public HttpContextBase Context { get { return _contextBase; } }
public HttpRequestBase Request { get { return _requestBase; } }
public HttpResponseBase Response { get { return _responseBase; } }
public HttpSessionStateBase Session { get { return _sessionStateBase; } }
public HttpServerUtilityBase Server { get { return _serverUtilityBase; } }
public HttpContextMock()
{
_contextBase = MockRepository.GenerateStub<HttpContextBase>();
_requestBase = MockRepository.GenerateStub<HttpRequestBase>();
_responseBase = MockRepository.GenerateStub<HttpResponseBase>();
_sessionStateBase = MockRepository.GenerateStub<HttpSessionStateBase>();
_serverUtilityBase = MockRepository.GenerateStub<HttpServerUtilityBase>();
_contextBase.Stub(x => x.Request).Return(_requestBase);
_contextBase.Stub(x => x.Response).Return(_responseBase);
_contextBase.Stub(x => x.Session).Return(_sessionStateBase);
_contextBase.Stub(x => x.Server).Return(_serverUtilityBase);
_requestBase.Stub(x => x.IsAuthenticated).Return(true);
_contextBase.User = new GenericPrincipal(new GenericIdentity("a#a.a"),
new string[] {"Admin"});
}
}
And in SetUp of test I create an instance of a controller:
[SetUp]
public override void TestSetUp()
{
base.TestSetUp();
repository = MockRepository.GenerateMock<IFooRepository>();
controller = new FooController()
{
FooRepository = repository,
UserRepository = userMockRepository
};
controller.ControllerContext = new ControllerContext(context.Context, new RouteData(), controller);
}
And all is working fine, I can add parameters to session and many other things.
Hope this helps.
I am using shanselmann's MvcMockHelper class to mock up some HttpContext stuff using Moq but the issue I am having is being able to assign something to my mocked session object in my MVC controller and then being able to read that same value in my unit test for verification purposes.
My question is how do you assign a storage collection to the mocked session object to allow code such as session["UserName"] = "foo" to retain the "foo" value and have it be available in the unit test.
I started with Scott Hanselman's MVCMockHelper, added a small class and made the modifications shown below to allow the controller to use Session normally and the unit test to verify the values that were set by the controller.
/// <summary>
/// A Class to allow simulation of SessionObject
/// </summary>
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; }
}
}
//In the MVCMockHelpers I modified the FakeHttpContext() method as shown below
public static HttpContextBase FakeHttpContext()
{
var context = new Mock<HttpContextBase>();
var request = new Mock<HttpRequestBase>();
var response = new Mock<HttpResponseBase>();
var session = new MockHttpSession();
var server = new Mock<HttpServerUtilityBase>();
context.Setup(ctx => ctx.Request).Returns(request.Object);
context.Setup(ctx => ctx.Response).Returns(response.Object);
context.Setup(ctx => ctx.Session).Returns(session);
context.Setup(ctx => ctx.Server).Returns(server.Object);
return context.Object;
}
//Now in the unit test i can do
AccountController acct = new AccountController();
acct.SetFakeControllerContext();
acct.SetBusinessObject(mockBO.Object);
RedirectResult results = (RedirectResult)acct.LogOn(userName, password, rememberMe, returnUrl);
Assert.AreEqual(returnUrl, results.Url);
Assert.AreEqual(userName, acct.Session["txtUserName"]);
Assert.IsNotNull(acct.Session["SessionGUID"]);
It's not perfect but it works enough for testing.
Using Moq 3.0.308.2 here is an example of my account controller setup in my unit test:
private AccountController GetAccountController ()
{
.. setup mocked services..
var accountController = new AccountController (..mocked services..);
var controllerContext = new Mock<ControllerContext> ();
controllerContext.SetupGet(p => p.HttpContext.Session["test"]).Returns("Hello World");
controllerContext.SetupGet(p => p.HttpContext.User.Identity.Name).Returns(_testEmail);
controllerContext.SetupGet(p => p.HttpContext.Request.IsAuthenticated).Returns(true);
controllerContext.SetupGet(p => p.HttpContext.Response.Cookies).Returns(new HttpCookieCollection ());
controllerContext.Setup (p => p.HttpContext.Request.Form.Get ("ReturnUrl")).Returns ("sample-return-url");
controllerContext.Setup (p => p.HttpContext.Request.Params.Get ("q")).Returns ("sample-search-term");
accountController.ControllerContext = controllerContext.Object;
return accountController;
}
then within your controller method the following should return "Hello World"
string test = Session["test"].ToString ();
I've made a slightly more elaborate Mock than the answer posted by #RonnBlack
public class HttpSessionStateDictionary : HttpSessionStateBase
{
private readonly NameValueCollection keyCollection = new NameValueCollection();
private readonly Dictionary<string, object> _values = new Dictionary<string, object>();
public override object this[string name]
{
get { return _values.ContainsKey(name) ? _values[name] : null; }
set { _values[name] = value; keyCollection[name] = null;}
}
public override int CodePage
{
get { throw new NotImplementedException(); }
set { throw new NotImplementedException(); }
}
public override HttpSessionStateBase Contents
{
get { throw new NotImplementedException(); }
}
public override HttpCookieMode CookieMode
{
get { throw new NotImplementedException(); }
}
public override int Count
{
get { return _values.Count; }
}
public override NameObjectCollectionBase.KeysCollection Keys
{
get { return keyCollection.Keys; }
}
public Dictionary<string, object> UnderlyingStore
{
get { return _values; }
}
public override void Abandon()
{
_values.Clear();
}
public override void Add(string name, object value)
{
_values.Add(name, value);
}
public override void Clear()
{
_values.Clear();
}
public override void CopyTo(Array array, int index)
{
throw new NotImplementedException();
}
public override bool Equals(object obj)
{
return _values.Equals(obj);
}
public override IEnumerator GetEnumerator()
{
return _values.GetEnumerator();
}
public override int GetHashCode()
{
return (_values != null ? _values.GetHashCode() : 0);
}
public override void Remove(string name)
{
_values.Remove(name);
}
public override void RemoveAll()
{
_values.Clear();
}
public override void RemoveAt(int index)
{
throw new NotImplementedException();
}
public override string ToString()
{
return _values.ToString();
}
public bool Equals(HttpSessionStateDictionary other)
{
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;
return Equals(other._values, _values);
}
}
I just found a nice example of how the Oxite team fakes their HttpSessionState and maintains a SessionStateItemCollection collection within that fake. This should work just as well as a moq in my case.
EDIT:
URL for this example is http://oxite.codeplex.com/sourcecontrol/changeset/view/33871?projectName=oxite#388065
I think you can set an expectation on the mock with a specific value it should return whatever. Mocks are not used as actual fakes but rather things that you can assert behavior on.
It sounds like you are actually looking for an adapter that you can wrap around the session that you can supply a different implementation during tests and during runtime it would return HttpContext Session items?
Does this make sense?
Thank you, #RonnBlack for your solution! In my case, I kept getting this exception because Session.SessionID was null:
System.NotImplementedException was unhandled by user code
HResult=-2147467263
Message=The method or operation is not implemented.
Source=System.Web
StackTrace:
at System.Web.HttpSessionStateBase.get_SessionID()
To solve this problem I implement #RonnBlack's code this way using the Moq Mock<HttpSessionStateBase> instead of his MockHttpSession:
private readonly MyController controller = new MyController();
[TestFixtureSetUp]
public void Init()
{
var session = new Mock<HttpSessionStateBase>();
session.Setup(s => s.SessionID).Returns(Guid.NewGuid().ToString());
var request = new Mock<HttpRequestBase>();
var response = new Mock<HttpResponseBase>();
var server = new Mock<HttpServerUtilityBase>();
// Not working - IsAjaxRequest() is static extension method and cannot be mocked
// request.Setup(x => x.IsAjaxRequest()).Returns(true /* or false */);
// use this
request.SetupGet(x => x.Headers).Returns(
new System.Net.WebHeaderCollection
{
{"X-Requested-With", "XMLHttpRequest"}
});
var context = new Mock<HttpContextBase>();
//context
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.SetupGet(x => x.Request).Returns(request.Object);
context.SetupGet(p => p.Request.Url).Returns(new Uri("http://www.mytesturl.com"));
var queryString = new NameValueCollection { { "code", "codeValue" } };
context.SetupGet(r => r.Request.QueryString).Returns(queryString);
controller.ControllerContext = new ControllerContext(context.Object, new RouteData(), controller);
}
For details, please see http://weblogs.asp.net/gunnarpeipman/using-moq-to-mock-asp-net-mvc-httpcontextbase
Just for Session easier way is to create Session object in parent class and use it like this
public class DalBl : IDalBl
{
public dynamic Session
{
get { return HttpContext.Current.Session; }
}
}
and in unitTest
var session = new Dictionary<string, object>();
var moq = new Moq.Mock<IDalBl>();
moq.Setup(d => d.Session).Returns(session);