How to write a test for accounts controller for forms authenticate - asp.net-mvc

Trying to figure out how to adequately test my accounts controller. I am having problem testing the successful logon scenario.
Issue 1) Am I missing any other tests.(I am testing the model validation attributes separately)
Issue 2) Put_ReturnsOverviewRedirectToRouteResultIfLogonSuccessAndNoReturnUrlGiven() and Put_ReturnsRedirectResultIfLogonSuccessAndReturnUrlGiven() test are not passing. I have narrowed it down to the line where i am calling _membership.validateuser(). Even though during my mock setup of the service i am stating that i want to return true whenever validateuser is called, the method call returns false.
Here is what I have gotten so far
AccountController.cs
[HandleError]
public class AccountController : Controller
{
private IMembershipService _membershipService;
public AccountController()
: this(null)
{
}
public AccountController(IMembershipService membershipService)
{
_membershipService = membershipService ?? new AccountMembershipService();
}
[HttpGet]
public ActionResult LogOn()
{
return View();
}
[HttpPost]
public ActionResult LogOn(LogOnModel model, string returnUrl)
{
if (ModelState.IsValid)
{
if (_membershipService.ValidateUser(model.UserName,model.Password))
{
if (!String.IsNullOrEmpty(returnUrl))
{
return Redirect(returnUrl);
}
return RedirectToAction("Index", "Overview");
}
ModelState.AddModelError("*", "The user name or password provided is incorrect.");
}
return View(model);
}
}
AccountServices.cs
public interface IMembershipService
{
bool ValidateUser(string userName, string password);
}
public class AccountMembershipService : IMembershipService
{
public bool ValidateUser(string userName, string password)
{
throw new System.NotImplementedException();
}
}
AccountControllerFacts.cs
public class AccountControllerFacts
{
public static AccountController GetAccountControllerForLogonSuccess()
{
var membershipServiceStub = MockRepository.GenerateStub<IMembershipService>();
var controller = new AccountController(membershipServiceStub);
membershipServiceStub
.Stub(x => x.ValidateUser("someuser", "somepass"))
.Return(true);
return controller;
}
public static AccountController GetAccountControllerForLogonFailure()
{
var membershipServiceStub = MockRepository.GenerateStub<IMembershipService>();
var controller = new AccountController(membershipServiceStub);
membershipServiceStub
.Stub(x => x.ValidateUser("someuser", "somepass"))
.Return(false);
return controller;
}
public class LogOn
{
[Fact]
public void Get_ReturnsViewResultWithDefaultViewName()
{
// Arrange
var controller = GetAccountControllerForLogonSuccess();
// Act
var result = controller.LogOn();
// Assert
Assert.IsType<ViewResult>(result);
Assert.Empty(((ViewResult)result).ViewName);
}
[Fact]
public void Put_ReturnsOverviewRedirectToRouteResultIfLogonSuccessAndNoReturnUrlGiven()
{
// Arrange
var controller = GetAccountControllerForLogonSuccess();
var user = new LogOnModel();
// Act
var result = controller.LogOn(user, null);
var redirectresult = (RedirectToRouteResult) result;
// Assert
Assert.IsType<RedirectToRouteResult>(result);
Assert.Equal("Overview", redirectresult.RouteValues["controller"]);
Assert.Equal("Index", redirectresult.RouteValues["action"]);
}
[Fact]
public void Put_ReturnsRedirectResultIfLogonSuccessAndReturnUrlGiven()
{
// Arrange
var controller = GetAccountControllerForLogonSuccess();
var user = new LogOnModel();
// Act
var result = controller.LogOn(user, "someurl");
var redirectResult = (RedirectResult) result;
// Assert
Assert.IsType<RedirectResult>(result);
Assert.Equal("someurl", redirectResult.Url);
}
[Fact]
public void Put_ReturnsViewIfInvalidModelState()
{
// Arrange
var controller = GetAccountControllerForLogonFailure();
var user = new LogOnModel();
controller.ModelState.AddModelError("*","Invalid model state.");
// Act
var result = controller.LogOn(user, "someurl");
var viewResult = (ViewResult) result;
// Assert
Assert.IsType<ViewResult>(result);
Assert.Empty(viewResult.ViewName);
Assert.Same(user,viewResult.ViewData.Model);
}
[Fact]
public void Put_ReturnsViewIfLogonFailed()
{
// Arrange
var controller = GetAccountControllerForLogonFailure();
var user = new LogOnModel();
// Act
var result = controller.LogOn(user, "someurl");
var viewResult = (ViewResult) result;
// Assert
Assert.IsType<ViewResult>(result);
Assert.Empty(viewResult.ViewName);
Assert.Same(user,viewResult.ViewData.Model);
Assert.Equal(false,viewResult.ViewData.ModelState.IsValid);
}
}
}

Figured out how to fix my tests.
[Fact]
public void Put_ReturnsRedirectToRouteResultForOverviewIfLogonSuccessAndNoReturnUrlGiven()
{
// Arrange
var mocks = new MockRepository();
var mockMembershipService = mocks.StrictMock<IMembershipService>();
using (mocks.Record())
{
Expect.Call(mockMembershipService.ValidateUser("", "")).IgnoreArguments().Return(true).Repeat.Any();
}
var controller = new AccountController(mockMembershipService);
var user = new LogOnModel();
// Act
ActionResult result;
using (mocks.Playback()){
result = controller.LogOn(user, null);
}
// Assert
Assert.IsType<RedirectToRouteResult>(result);
var redirectresult = (RedirectToRouteResult)result;
Assert.Equal("Overview", redirectresult.RouteValues["controller"]);
Assert.Equal("Index", redirectresult.RouteValues["action"]);
}

Related

ReturnsAsync in Moq is not working

Here is my code
public interface IUserManager
{
Task<int> PostUser(User user);
IQueryable<User> GetUserById(long userId);
}
public class UserManager : IUserManager
{
public UserManager(DataContext context)
{
this.DataContext = context;
}
public async Task<int> PostUser(User user)
{
this.DataContext.User.Add(user);
return await this.DataContext.SaveChangesAsync().ConfigureAwait(false);
}
public IQueryable<User> GetUserById(long userId)
{
return this.DataContext.User
.Where(userNotes => userNotes.UserId == userId).AsQueryable();
}
}
Controller:
public class UserController : BaseController
{
private readonly IUserManager userManager;
public UserController()
{
this.userManager = new UserManager();
}
public UserController(IUserManager userManager)
{
this.userManager = userManager;
}
[EnableQuery]
public IQueryable<User> Get([FromODataUri]long userId)
{
return this.userManager.GetUserById(userId);
}
public HttpResponseMessage Post(User user)
{
if (userNote == null || !ModelState.IsValid)
{
return this.BuildErrorResponse(ResponseCodes.INVALID_MISSING_INPUTS);
}
if (this.userManager.PostUser(user).Result <= 0)
{
return this.BuildErrorResponse(ResponseCodes.USER_ADD_FAILED);
}
return this.BuildSuccessResponse<User>(ResponseCodes.USER_ADDED, user);
}
}
Moq test:
[TestClass]
public class UnitTest
{
IUserManager userManagerMock;
Mock<IUserManager> iUserManagerMock;
[TestInitialize]
public void Setup()
{
//.. setup variables and mock data
userManagerMock = new UserManager(ContextMock.Object);
iUserManagerMock.Setup(u => u.PostUser(It.IsAny<User>()))
.ReturnsAsync(1);
}
[TestMethod]
public void Post()
{
var controller = new UserController(userManagerMock); //userNotesManagerMock will have the mock data //and mock methods setup
var httpResponse = controller.Post(userPostMock); //userPostMock will have mock data to save
//Assert.AreEqual(HttpResponseMessage, result);
}
}
I wrote a post method as you'd see here. I've a Get method as well which is working perfectly with mocking data.
But when I debug through the Post, the data the following statement is always returns ZERO instead of 1, which I've set up in the ReturnsAsync.
this.userManager.PostUser(user).Result <= 0 //this always gives me zero
What's wrong with the post ? Am I doing anything wrong ?
Can anyone shed some light on this
EDIT
Here is the GET version of the Test method and setup
[TestInitialize]
public void Setup()
{
//dummy data setup
UserMock = new List<User>
{
new User { //blah blah properties }
}.AsQueryable();
//context setup
dbSetUserMock = new Mock<IDbSet<User>>();
dbSetUserMock.Setup(m => m.Provider).Returns(UserMock.Provider);
dbSetUserMock.Setup(m => m.Expression).Returns(UserMock.Expression);
dbSetUserMock.Setup(m => m.ElementType).Returns(UserMock.ElementType);
dbSetUserMock.Setup(m => m.GetEnumerator()).Returns(UserMock.GetEnumerator());
UserContextMock = new Mock<DataContext>();
UserContextMock.Setup(s => s.User).Returns(dbSetUserMock.Object);
//inject the context to manager
UserManagerMock = new UserManager(UserContextMock.Object);
iUserManagerMock = new Mock<IUserManager>();
iUserManagerMock.Setup(notes => notes.PostUserNote(It.IsAny<User>()))
.ReturnsAsync(1);
}
[TestMethod]
public void Get_User_Success()
{
var controller = new UserController(UserManagerMock);
var values = controller.Get(100);
Assert.AreEqual(100, values.FirstOrDefault().UserId);
}
Get_User_Success works well now with the dummy data set and I'm able to pass the test without iUserManagerMock object. Reason being I want to execute the code inside of GetUser() method in Manager class.
And I've another question over here. As I'm setting the expected result to be 1 in the ReturnAsync for Post action. How do I build a test method for failure case scenario as the return from the POST method will still be 1 for failure case ?
You are mixing up the set up. If the method under test is the UserController.Post and you want to mock the UserManager via the IUserManager interface, then you don't need to new one up manually.
[TestClass]
public class UnitTest {
[TestMethod]
public void UserController_Should_Post_User_Ok() {
//Arrange
var userPostMock = new User { UserId = 100 };
var userManagerMock = new Mock<IUserManager>();
userManagerMock.Setup(u => u.PostUser(It.IsAny<User>())).ReturnsAsync(1);
var controller = new UserController(userManagerMock.Object);
controller.Request = new HttpRequestMessage();
controller.Configuration = new HttpConfiguration();
//Act
var httpResponse = controller.Post(userPostMock);
//Assert
Assert.AreEqual(System.Net.HttpStatusCode.OK, httpResponse.StatusCode);
}
[TestMethod]
public void UserController_Should_Post_User_Fail() {
//Arrange
var userPostMock = new User { UserId = 100 };
var userManagerMock = new Mock<IUserManager>();
userManagerMock.Setup(u => u.PostUser(It.IsAny<User>())).ReturnsAsync(0);//Replicating a failed post
var controller = new UserController(userManagerMock.Object);
controller.Request = new HttpRequestMessage();
controller.Configuration = new HttpConfiguration();
//Act
var httpResponse = controller.Post(userPostMock);
//Assert
Assert.AreEqual(System.Net.HttpStatusCode.InternalServerError, httpResponse.StatusCode);
}
[TestMethod]
public void UserManager_Should_Get_User_Success() {
//Arrange
var userMock = new List<User>
{
new User (){ UserId=100 }
}.AsQueryable();
//context setup
var dbSetUserMock = new Mock<IDbSet<User>>();
dbSetUserMock.Setup(m => m.Provider).Returns(userMock.Provider);
dbSetUserMock.Setup(m => m.Expression).Returns(userMock.Expression);
dbSetUserMock.Setup(m => m.ElementType).Returns(userMock.ElementType);
dbSetUserMock.Setup(m => m.GetEnumerator()).Returns(userMock.GetEnumerator());
var userContextMock = new Mock<DataContext>();
userContextMock.Setup(s => s.User).Returns(dbSetUserMock.Object);
//inject the context to manager
var userManagerMock = new UserManager(userContextMock.Object);
//Act
var values = userManagerMock.GetUserById(100);
//Assert
Assert.AreEqual(100, values.FirstOrDefault().UserId);
}
}

Trying to register a user from another controller other than AccountController gives an error

Totally messed up my last question so posting a new one.
MyTestController:
[HttpPost]
public async Task<ActionResult> Index(MyTestViewModel viewModel)
{
if (ModelState.IsValid)
{
AccountController ac = new AccountController();
var user = new ApplicationUser()
{
UserName = viewModel.Email
};
var result = await ac.UserManager.CreateAsync(user, viewModel.Password);
if (result.Succeeded)
{
await ac.SignInAsync(user, isPersistent: true);
}
else
{
ac.AddErrors(result);
}
The SignInAsync method in AccountController (changed this from private to public):
public async Task SignInAsync(ApplicationUser user, bool isPersistent)
{
AuthenticationManager.SignOut(DefaultAuthenticationTypes.ExternalCookie);
var identity = await UserManager.CreateIdentityAsync(user, DefaultAuthenticationTypes.ApplicationCookie);
AuthenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = isPersistent }, identity);
}
While trying to register the user it gives me the following error:
Exception Details: System.NullReferenceException: Object reference not set to an instance of an object.
Source Error:
Line 411: get
Line 412: {
Line 413: return HttpContext.GetOwinContext().Authentication;
Line 414: }
Line 415: }
Those lines in AccountController:
private IAuthenticationManager AuthenticationManager
{
get
{
return HttpContext.GetOwinContext().Authentication;
}
}
Everything in the AccountController is default MVC 5 app stuff.
It is not possible to call these methods from another controller, like in my example above?
And why am I getting a NullReferenceException on line 413?
Calling a Controller method from another Controller is difficult because of properties like the HttpContext which need to be properly initialized. This is usually done by the MVC framework which creates the controller using a ControllerFactory and at some point during this process the protected method Initialize is called on the controller which ensures that the HttpContext property is set.
This is why you get the exception on line 413, because the Initialize method hasn't been called on the controller you created using the new operator.
I think it would be easier to refactor out the functionality you want to share.
E.g. if both AccountController and your MyTestController holds a reference to something like this
public class AccountManager
{
public UserManager<ApplicationUser> UserManager { get; private set; }
public HttpContextBase HttpContext { get; private set; }
public AccountManager()
: this(new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(new ApplicationDbContext())))
{
}
public AccountManager(UserManager<ApplicationUser> userManager)
{
UserManager = userManager;
}
public void Initialize(HttpContextBase context)
{
HttpContext = context;
}
private IAuthenticationManager AuthenticationManager
{
get
{
return HttpContext.GetOwinContext().Authentication;
}
}
public async Task SignInAsync(ApplicationUser user, bool isPersistent)
{
AuthenticationManager.SignOut(DefaultAuthenticationTypes.ExternalCookie);
var identity = await UserManager.CreateIdentityAsync(user, DefaultAuthenticationTypes.ApplicationCookie);
AuthenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = isPersistent }, identity);
}
}
You would then modify the AccountController like this:
public class AccountController : Controller
{
public AccountController()
: this(new AccountManager())
{
}
public AccountController(AccountManager accountManager)
{
AccountManager = accountManager;
}
protected override void Initialize(System.Web.Routing.RequestContext requestContext)
{
base.Initialize(requestContext);
AccountManager.Initialize(this.HttpContext);
}
public UserManager<ApplicationUser> UserManager
{
get
{
return AccountManager.UserManager;
}
}
public AccountManager AccountManager { get; private set; }
And your MyTestController would be like this:
public class MyTestController : Controller
{
public MyTestController ()
: this(new AccountManager())
{
}
public MyTestController (AccountManager accountManager)
{
AccountManager = accountManager;
}
protected override void Initialize(System.Web.Routing.RequestContext requestContext)
{
base.Initialize(requestContext);
AccountManager.Initialize(this.HttpContext);
}
public AccountManager AccountManager { get; private set; }
[HttpPost]
public async Task<ActionResult> Index(MyTestViewModel viewModel)
{
if (ModelState.IsValid)
{
var user = new ApplicationUser()
{
UserName = viewModel.Email
};
var result = await AccountManager.UserManager.CreateAsync(user, viewModel.Password);
if (result.Succeeded)
{
await AccountManager.SignInAsync(user, isPersistent: true);
}
else
{
AddErrors(result); //don't want to share this a it updates ModelState which belongs to the controller.
}
Update:
Had to make som minor changes:
I had to change the UserManager property since the Dispose method uses the setter method:
private UserManager<ApplicationUser> _userManager;
public UserManager<ApplicationUser> UserManager
{
get { return AccountManager.UserManager; }
private set { _userManager = value; }
}
protected override void Dispose(bool disposing)
{
if (disposing && UserManager != null)
{
UserManager.Dispose();
UserManager = null;
}
base.Dispose(disposing);
}
I had to add the AddErrors method to MyTestController (as you pointed out that we don't want to share that method):
private void AddErrors(IdentityResult result)
{
foreach (var error in result.Errors)
{
ModelState.AddModelError("", error);
}
}
I re-added this line to the AccountManager property in the AccountManager class (not really related to the question but I had it in my project)
UserManager.UserValidator = new UserValidator(UserManager) { AllowOnlyAlphanumericUserNames = false };
For me it working like a charm after setting the current ControllerContext to AccountControllerContext. Not sure if there are any drawbacks with this approach.
//This is employee controller class
public ActionResult Create([Bind(Include = "EmployeeId,FirstName,LastName,DOJ,DOB,Address,City,State,Mobile,Landline,ReportsTo,Salary")] Employee employee)
{
if (ModelState.IsValid)
{
AccountController accountController = new AccountController();
accountController.ControllerContext = this.ControllerContext;
//accountController.UserManager;
var userId = accountController.RegisterAccount(new RegisterViewModel { Email = "temp#temp.com", Password = "Pravallika!23" });
if (!string.IsNullOrEmpty(userId))
{
employee.UserId = userId;
employee.CreatedBy = User.Identity.GetUserId();
db.Employees.Add(employee);
db.SaveChanges();
return RedirectToAction("Index");
}
}
//customized method in AccountController
public string RegisterAccount(RegisterViewModel model)
{
if (ModelState.IsValid)
{
var user = new ApplicationUser() { UserName = model.Email, Email = model.Email };
IdentityResult result = UserManager.Create(user, model.Password);
//to add roles
//UserManager.AddToRole(user.Id, "Admin");
if (result.Succeeded)
{
return user.Id;
}
else
{
AddErrors(result);
}
}
// If we got this far, something failed
return null;
}

Unit testing controller using MOQ . How to mock httpcontext

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"]);
}
}

How to get result from ControllerContext

My controller action returns a custom ActionResult that executes either a success or failure result depending on some validation logic. This happens within ExecuteResult.
My question is, how can I check the result?
Here's the test so far:
[TestFixture]
public class FormActionResultTests
{
TestController controller;
[SetUp]
public void SetUp()
{
ObjectFactory.Initialize(cfg =>
{
cfg.For<IFormHandler<TestModel>>().Use<TestModelHandler>();
});
controller = new TestControllerBuilder().CreateController<TestController>();
}
[Test]
public void Valid_input_returns_success_result()
{
var result = controller.Test(new TestModel { IsValid = true, IsValid2 = true })
.AssertResultIs<FormActionResult<TestModel>>();
var context = controller.ControllerContext;
result.ExecuteResult(context);
// how to verify result?
}
}
public class TestController : Controller
{
public ActionResult Test(TestModel model) {
return new FormActionResult<TestModel>(model, this.Content("Success"), View(model));
}
}
public class TestModel {
public bool IsValid { get; set; }
public bool IsValid2 { get; set; }
}
public class TestModelHandler : IFormHandler<TestModel>
{
public void Handle(TestModel form, IValidationDictionary validationDictionary)
{
}
}
Update
Here's what worked for me in the end (using NSubstitute):
[Test]
public void Valid_input_returns_success_result()
{
var result = new FormActionResult<TestModel>(new TestModel { IsValid = true, IsValid2 = true },
new ContentResult { Content = "Success" }, new ContentResult { Content = "Failed" });
var sb = new StringBuilder();
var response = Substitute.For<HttpResponseBase>();
response.When(x => x.Write(Arg.Any<string>())).Do(ctx => sb.Append(ctx.Arg<string>()));
var httpContext = Substitute.For<HttpContextBase>();
httpContext.Response.Returns(response);
var controllerContext = new ControllerContext(httpContext, new RouteData(), new TestController());
result.ExecuteResult(controllerContext);
sb.ToString().ShouldEqual("Success");
}
Controller should be tested that they return correct ActionResult in your case, and the Success or Failure of ActionResult should be tested by ActionResultTest and it has nothing to do with controller. Unit test means single unit test - but you test both controller and ActionResult in the same test, that is incorrect. To test ActionResult, first imagine that generally all that ActionResult does, is writing result to HttpResponse. Let's rewrite your code to use Moq to supply StringWriter for ControllerContext.HttpContext.HttpResponse.Output
[Test]
public void Valid_input_returns_success_result()
{
var result = controller.Test(new TestModel { IsValid = true, IsValid2 = true })
.AssertResultIs<FormActionResult<TestModel>>();
var context = controller.ControllerContext;
Mock<HttpContextBase> mockHttpContext = new Mock<HttpContextBase>();
StringWriter actionResultOutput = new StringWriter();
mockHttpContext.Setup(x => x.Response.Output).Returns(actionResultOutput);
context.HttpContext = mockHttpContext.Object;
result.ExecuteResult(context);
// how to verify result? Examine actionResultOutput
}
All is left to examine actionResultOutput. For example, if your action result is designed to return string "Success" when validation is ok and "Error" when validation failed, compare these strings to actionResultOutput.ToString(). If your result view's generated html is more complex, you can use HtmlAgilityPack to examine output more deeply
You should write a simple unit test of the Test-action, asserting on the returned action result. You shouldn't depend on the MVC framework in your test. Simply create a new instance of TestController and call the Test method.

Mocking a Customer SessionHandler Object in ASP.NET MVC for Unittests with Rhino Mocks

I currently use the following approach to create a strongly typed object representing session variables.
public abstract class SessionController : Controller
{
private const string SESSION_NAME = "UserSession";
public SessionData SessionData
{
get
{
SessionData sessionData = (SessionData)Session[SESSION_NAME];
if (sessionData != null)
{
return sessionData;
}
else
{
sessionData = new SessionData();
Session[SESSION_NAME] = sessionData;
return sessionData;
}
}
set
{
Session[SESSION_NAME] = value;
}
}
}
SessionData is a simple object like for example
[Serializable]
public class SessionData
{
public String SessionId { get; set; }
public Int64 UserId { get; set; }
public String NameOfUser { get; set; }
}
When creating a new Controller I derivate it from the SessionController so that I have strongley typed access to my SessionData. For example
public CityController : SessionController
{
[AcceptVerbs(HttpVerbs.Get)]
public ActionResult Index()
{
ViewData.Model = _cityService.GetAll(SessionData.UserId);
return View("Index");
}
}
So, I am struggling at the moment to get this approached covered by a unittest. A shortened version of what I have tried is the following snippet
[SetUp]
public void SetUp()
{
mocks = new MockRepository();
_cityService = MockRepository.GenerateStub<ICityService>();
_sesssionData = new SessionData { UserId = 1, SessionId = "1" };
// First Approach
controller = new CityController(_cityService);
controller.Expect(p => p.SessionData).Return(_sesssionData);
// Second Approach
cctx = MockRepository.GenerateStub<ControllerContext>();
cctx.Expect(p=>p.HttpContext.Session["UserSession"] as SessionData).Return(_sesssionData);
controller.ControllerContext = cctx;
}
Has anyone a tip on how to get this problem solved?
If you make your SessionData property virtual then your first approach could work:
// Arrange
var mocks = new MockRepository();
var cityService = MockRepository.GenerateStub<ICityService>();
var sesssionData = new SessionData { UserId = 1, SessionId = "1" };
var controller = mocks.PartialMock<CityController>(cityService);
controller.Expect(c => c.SessionData).Return(sessionData);
controller.Replay();
// Act
controller.Index();
//Assert
...
IMO this approach is not very good because the SUT (Subject Under Test => in this case CityController) is being mocked with PartialMock.

Resources