Writing unit tests that require database access via my CustomMembershipProvider.
edit -
public class CustomMembershipProvider : MembershipProvider
{
public override bool ValidateUser(string username, string password)
{
using (var usersContext = new UsersContext())
{
var requiredUser = usersContext.GetUser(username, password);
var userApproved = usersContext.GetUserMem(username);
if (userApproved == null) return false;
return (requiredUser != null && userApproved.IsApproved != false);
}
}
}
[TestFixture]
public class AccountControllerTest
{
[Test]
public void ShouldNotAcceptInvalidUser()
{
// OPTION1
Mock<IMembershipService> membership = new Mock<IMembershipService>();
//OPTION2
// Mock<AccountMembershipService> membership = new Mock<AccountMembershipService>();
membership.Setup(m => m.ValidateUser(It.IsAny<string>(), It.IsAny<string>()))
.Returns(false);
var logonModel = new LoginModel() { EmailorUserName = "connorgerv", Password = "pasdsword1" };
var controller = new AccountController(membership.Object);
// Act
var result = controller.Login(logonModel,"Index") as RedirectResult;
// Assert
Assert.That(result.Url, Is.EqualTo("Index"));
Assert.False(controller.ModelState.IsValid);
Assert.That(controller.ModelState[""],
Is.EqualTo("The user name or password provided is incorrect."));
}
[Test]
public void ExampleForMockingAccountMembershipService()
{
var validUserName = "connorgerv";
var validPassword = "passwordd1";
var stubService = new Mock<CustomMembershipProvider>();
bool val = false;
stubService.Setup(x => x.ValidateUser(validUserName, validPassword)).Returns(true);
Assert.IsTrue(stubService.Object.ValidateUser(validUserName, validPassword));
}
}
public class AccountController : Controller
{
public IMembershipService MembershipService { get; set; }
public AccountController(IMembershipService service){
MembershipService=service;
}
protected override void Initialize(RequestContext requestContext)
{
if (MembershipService == null) { MembershipService = new AccountMembershipService(); }
base.Initialize(requestContext);
}
public ActionResult Index()
{
return RedirectToAction("Profile");
}
public ActionResult Login()
{
if (User.Identity.IsAuthenticated)
{
//redirect to some other page
return RedirectToAction("Index", "Home");
}
return View();
}
//
// POST: /Account/Login
[HttpPost]
public ActionResult Login(LoginModel model, string ReturnUrl)
{
if (ModelState.IsValid)
{
if (MembershipService.ValidateUser(model.EmailorUserName, model.Password))
{
SetupFormsAuthTicket(model.EmailorUserName, model.RememberMe);
if (Url.IsLocalUrl(ReturnUrl) && ReturnUrl.Length > 1 && ReturnUrl.StartsWith("/")
&& !ReturnUrl.StartsWith("//") && !ReturnUrl.StartsWith("/\\"))
{
return Redirect(ReturnUrl);
}
return RedirectToAction("Index", "Home");
}
ModelState.AddModelError("", "The user name or password provided is incorrect.");
}
// If we got this far, something failed, redisplay form
return View(model);
}
}
public class AccountMembershipService : IMembershipService
{
private readonly MembershipProvider _provider;
public AccountMembershipService()
: this(null)
{
}
public AccountMembershipService(MembershipProvider provider)
{
_provider = provider ?? Membership.Provider;
}
public virtual bool ValidateUser(string userName, string password)
{
if (String.IsNullOrEmpty(userName)) throw new ArgumentException("Value cannot be null or empty.", "userName");
if (String.IsNullOrEmpty(password)) throw new ArgumentException("Value cannot be null or empty.", "password");
return _provider.ValidateUser(userName, password);
}
}
Membership in web.config of main application
<membership defaultProvider="CustomMembershipProvider">
<providers>
<clear />
<add name="CustomMembershipProvider" type="QUBBasketballMVC.Infrastructure.CustomMembershipProvider" connectionStringName="UsersContext" enablePasswordRetrieval="false" enablePasswordReset="true" requiresQuestionAndAnswer="false" requiresUniqueEmail="false" maxInvalidPasswordAttempts="5" minRequiredPasswordLength="6" minRequiredNonalphanumericCharacters="0" passwordAttemptWindow="10" applicationName="/" />
</providers>
</membership>
public class CustomMembershipProvider : MembershipProvider
{
public override bool ValidateUser(string username, string password)
{
using (var usersContext = new UsersContext())
{
var requiredUser = usersContext.GetUser(username, password);
var userApproved = usersContext.GetUserMem(username);
if (userApproved == null) return false;
return (requiredUser != null && userApproved.IsApproved != false);
}
}
}
What happens when I run ShouldNotAcceptInvalidUser() with Option1 uncommented I can see that MembershipService is a Mock<IMembershipService> in the AccountController but it never steps into MembershipService.ValidateUser on the login Action.
When I run with option2 uncommented the same thing happens except MembershipService is Mock<AccountMembershipService> in the accountcontroller and it hits the AccountMembership Contstructor with null parameters, which in turn sets is to SqlMembershipProvider as Membership.Provider is System.Web.Security.SqlMembershipProvider
Also ExampleForMockingAccountMembershipService() doesn't seem to hit the ValidateUsermethod at all in CustomMembershipProvider and always returns true.
Hopefully this is enough to see where i'm going wrong!! :/
Thanks for providing your code. I think I have a much better handle on what you're trying to do now.
For your ShouldNotAcceptInvalidUser test, you should definitely mock IMembershipService instead of AccountMembershipService (choose option 1 over option 2). Since your controller is your SUT, it should be the only "real" class in the test, in order to minimize the number of moving parts.
With option 1, there's no reason to expect that MembershipService.ValidateUser would step into any code. The MembershipService is a mock object - and you've explicitly told it to just always return false when that method is called. Based on the code here, and using option 1, I'd expect this test to pass.
In your other test, ExampleForMockingAccountMembershipService, you're mocking your SUT which is something you should not do. Your SUT should be the only "real" object in your test. That means all collaborating objects should be mocked, leaving the SUT to be the only object doing anything meaningful. (That way, if the test fails, you know for sure that it's because of a bug in the SUT.)
(Note: ValidateUser was always returning true here because you mocked the SUT, and explicitly told it to always return true. This is why it's never a good idea to mock your SUT - mocking changes the behavior that you're trying to test.)
Based on the code you provided, I'm guessing that the reason you mocked CustomMembershipProvider is because it doesn't fully implement its abstract base class MembershipService. If this is indeed the case, then you will need to implement the missing methods manually, instead of relying on the mocking framework to provide default implementations.
Here is what I believe you were intending this test to look like:
[Test]
public void ExampleForMockingAccountMembershipService()
{
var validUserName = "connorgerv";
var validPassword = "passwordd1";
var sut = new CustomMembershipProvider();
Assert.IsTrue(sut.ValidateUser(validUserName, validPassword));
}
Something to look out for here is the fact that CustomMembershipProvider instantiates one of its dependencies: UsersContext. In a unit test, since CustomMembershipProvider is your SUT, you'd want to mock all of its dependencies. In this situation, you could use dependency injection to pass an object responsible for creating this dependency (e.g., an IUsersContextFactory), and use a mock factory and context in your test.
If you don't want to go that route, then just be aware that your test could fail because of a bug in CustomMembershipProvider or a bug in UsersContext.
So, the general logic in your tests is sound; the problems mainly stem from confusion on the role of mock objects in your tests. It's kind of a tough concept to get at first, but here are some resources that helped me when I was learning this:
"Dependency Injection in .Net" by Mark Seemann
"Test Doubles" by Martin Fowler
Related
I am getting
"A second operation started on this context before a previous
asynchronous operation completed"
error intermittently in an MVC project that uses asp.net identity 2.1. Basically I have a base controller where every other controller derives from, and this controller has a property that returns an ApplicationUser. (The extended class of asp.net identity user). The base controller has the following. Note that UserManager is retrieved from the OwinContext (and yes, it is created by using
CreatePerOwinContext()
method, just like in the templates.
protected ApplicationUser ApplicationUser
{
get
{
var userId = User.Identity.GetUserId();
var applicationUser = UserManager.FindById(userId);
if (applicationUser == null)
{
return null;
}
return applicationUser;
}
}
protected ApplicationUserManager UserManager
{
get
{
return _userManager ?? HttpContext.GetOwinContext().GetUserManager<ApplicationUserManager>();
}
set
{
_userManager = value;
}
}
Then in my other controllers, I freely call this property to get a reference to the ApplicationUser, an example being as follows
public ActionResult Index()
{
if (Request.IsAuthenticated)
{
var model = new OrganizationListViewModel(false)
{
OrganizationDetailViewModels = ApplicationUser.Affiliations
.Select(e => new OrganizationDetailViewModel(
e.Organization, ApplicationUser)).ToList()
};
return View(model);
}
else
{
return RedirectToAction("Search", "Search", new { type = "organization" });
}
}
This sometimes works, and sometimes it gives the error I mentioned. I am not calling an async operation in the property or the controller method. So I am puzzled. Any ideas?
I suspect this line:
OrganizationDetailViewModels = ApplicationUser.Affiliations
.Select(e => new OrganizationDetailViewModel(
e.Organization, ApplicationUser)).ToList()
Here you do a request for ApplicationUser, then lazy-load Affiliations, then for every select in Affiliations you do a request for ApplicationUser again.
Do local caching for the ApplicationUser to avoid multiple requests to DB where only one is required:
private ApplicationUser _cachedUser;
protected ApplicationUser ApplicationUser
{
get
{
var userId = User.Identity.GetUserId();
if (_cachedUser == null)
{
var applicationUser = UserManager.FindById(userId);
return applicationUser;
}
return _cachedUser;
}
}
Implementing a basic authorization and authentication layer is quite easy with ASP.NET MVC 4; it's all automatically generated with the 'ASP.NET MVC 4 Web Application'-project template.
However, I'm tasked with implementing some controller actions that require re-authentication and I'm aiming for a maintainable solution. Simply put in a user story, I'm trying to implement the following:
User logs on;
User navigates to a controller (attributed with [Authorize]) action which renders a form view;
User performs a POST by submitting the form;
An authentication form appears in which the user needs to re-authenticate using his/her username and password;
If authentication is succesfull, proceed with handling the POST-request.
Note that 'reauthentication' does not have to alter the state of the current user session.
Obviously, there are many ways to implementing this, but I feel like an implementation which looks similiar to the following (pseudo) sample would suit my needs.
[Authorize]
[InitializeSimpleMembership]
public class SpecialActionController : Controller
{
public ActionResult SpecialForm() { return View(); }
public ActionResult Succes() { return View(); }
[HttpPost]
[ReAuthenticate] /* <- Prompts user with reauthentication form before proceeding. */
public ActionResult SpecialForm(SpecialFormModel model)
{
if (ModelState.IsValid)
RedirectToAction("Succes");
else
return View(model);
}
}
Any suggestions?
Edit: I forgot to mention that any OAuth-related features are out of scope. External authentication is not an issue here and does not require support. In fact, with the current project I'm working on, all OAuth-related features are either removed or deactivated.
You should be able to do this using a combination of a custom AuthorizeAttribute and the Session. Override the AuthorizeCore method and let all the default authentication take place but introduce your own extra check (for re-authentication) e.g.
public class RecurringAuthorizeAttribute : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
var reauth = (bool?)httpContext.Session["ReAuthenticated"];
var result = base.AuthorizeCore(httpContext) && (reauth ?? false);
httpContext.Session["ReAuthenticated"] = !result;
return result;
}
}
This should re-direct the user to the login page everytime they hit the action and they haven't re-authenticated. If the user has re-authenticated, we clear the session variable to force a login on the next request.
For this to work correctly, we need a hook to set the ReAuthentication session variable - I think the LogOn method in the AccountController would be the ideal place for this
public class AccountController : Controller
{
...
[HttpPost]
public ActionResult LogOn(LogOnModel model, string returnUrl)
{
if (ModelState.IsValid && WebSecurity.Login(model.UserName, model.Password, persistCookie: model.RememberMe))
{
Session["ReAuthenticated"] = User.Identity.IsAuthenticated;
return RedirectToLocal(returnUrl);
}
...
}
}
Then all that's left to do is decorate our controller actions
[Authorize]
public ActionResult SomePrivateAction()
{
...
}
[RecurringAuthorize]
public ActionResult SomeSuperSecretAction()
{
...
}
You should find authorization will work as normal for any actions using the default AuthorizeAttribute and any actions decorated with the RecurringAuthorizeAttribute will be forced to login everytime they request the page, which includes page refreshes.
I tried to implement the hypothetical [ReAuthenticate]-attribute, but found myself relying on reflection too much. After putting some thought into a more manageable solution, I finally came up with the following:
ReAuth class
public sealed class ReAuth
{
#region Constructor
private ReAuth(Func<System.Web.Mvc.ActionResult> onSuccessAction)
{
this.onSuccessAction = onSuccessAction;
}
#endregion
#region Public static members
public static ReAuth CreateFor(HttpSessionStateBase httpSession, Func<System.Web.Mvc.ActionResult> onSuccessAction)
{
httpSession[sessionKey] = new ReAuth(onSuccessAction);
return GetFor(httpSession);
}
public static ReAuth GetFor(HttpSessionStateBase httpSession)
{
return httpSession[sessionKey] as ReAuth;
}
public static bool ExistsFor(HttpSessionStateBase httpSession)
{
return httpSession[sessionKey] as ReAuth != null;
}
#endregion
#region Public instance members
public bool ReAuthenticated { get; set; }
public System.Web.Mvc.ActionResult Handle()
{
if (ReAuthenticated)
return onSuccessAction();
else
return new System.Web.Mvc.RedirectToRouteResult(
new System.Web.Routing.RouteValueDictionary
{
{ "Controller", "#" }, /* Replace '#' with the name of the controller that implements the re-authentication form... */
{ "Action", "#" } /* Replace '#' with the name of the action on the aforementioned controller. */
});
}
#endregion
#region Private members
private const string sessionKey = "reAuthenticationSessionKey";
private readonly Func<System.Web.Mvc.ActionResult> onSuccessAction;
#endregion
}
Implementation
Suppose we have a hypothetical controller where the solution is applied:
public class AccountInfoController : System.Web.Mvc.Controller
{
/* snip... */
[HttpPost]
public ActionResult EditAccountInfo(AccountInfo model)
{
if (ModelState.IsValid)
return ReAuth.CreateFor(Session, () => { return Success(); }).Handle();
else
return View(model);
}
}
...and, we need a controller (essentially, a 'dumb' copy of the real AccountController that does not tamper with the Forms Authentication User Session state) in which the re-authentication takes place.:
public class ReAuthController : System.Web.Mvc.Controller
{
/* Snip... */
[HttpPost]
public ActionResult LogOn(LogOnModel model)
{
if (ModelState.IsValid)
{
ReAuth.GetFor(Session).ReAuthenticated = Membership.ValidateUser(model.User, model.Password);
return ReAuth.Handle();
}
return View(model);
}
}
As far as I know, this is a manageable solution. It does rely a lot on storing objects into session state. (Especially the object state of the controller which implements the ReAuth-class) If anyone has additional suggestions, please let me know!
I was wondering whether any people could help me please? I'm trying to create a site where the user logs in, it retrieves their chosen language from the database, and it uses that when setting the culture. There are also a number of settings about the user that would be retrieved at the same time as the user's language.
The culture/translations are handled via a base controller below (it's still a test version, but you will get the idea).
public abstract class BaseController : Controller
{
//public UserRegistrationInformation UserSession;
//public void GetUserInfo()
//{
// WebUsersEntities db = new WebUsersEntities();
// UserSession = db.UserRegistrationInformations.Where(r => r.uri_UserID == WebSecurity.CurrentUserId).FirstOrDefault();
//}
protected override IAsyncResult BeginExecuteCore(AsyncCallback callback, object state)
{
//GetUserInfo();
string cultureName = null;
// Change this to read from the user settings rather than a cookie
/// Attempt to read the culture cookie from Request
//HttpCookie cultureCookie = Request.Cookies["_culture"];
//if (cultureCookie != null)
// cultureName = cultureCookie.Value;
//else
cultureName = Request.UserLanguages[0]; // obtain it from HTTP header AcceptLanguages
//cultureName = "es-es";
// Validate culture name
cultureName = CultureHelper.GetImplementedCulture(cultureName); // This is safe
// Modify current thread's cultures
Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo(cultureName);
Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture;
return base.BeginExecuteCore(callback, state);
}
}
This was largely taken from http://afana.me/post/aspnet-mvc-internationalization-part-2.aspx
I've been searching for how to pass the user's settings to the _layout rather than just the view. I found an interesting post here Pass data to layout that are common to all pages that works for me, I've created a base ViewModel, and any other ViewModels are inheriting it.
public abstract class ViewModelBase
{
public string BrandName { get; set; }
public UserRegistrationInformation UserSession;
public void GetUserInfo()
{
WebUsersEntities db = new WebUsersEntities();
UserSession = db.UserRegistrationInformations.Where(r => r.uri_UserID == WebSecurity.CurrentUserId).FirstOrDefault();
}
}
To test with I've altered the existing change password model and control to:
public class LocalPasswordModel : ViewModelBase
{..........}
and
public ActionResult Manage(ManageMessageId? message)
{
//ViewModelAccounts vma = new ViewModelAccounts();
//vma.GetUserInfo();
LocalPasswordModel l = new LocalPasswordModel();
l.GetUserInfo();
l.BrandName = "blue";
ViewBag.StatusMessage =
message == ManageMessageId.ChangePasswordSuccess ? "Your password has been changed."
: message == ManageMessageId.SetPasswordSuccess ? "Your password has been set."
: message == ManageMessageId.RemoveLoginSuccess ? "The external login was removed."
: "";
ViewBag.HasLocalPassword = OAuthWebSecurity.HasLocalAccount(WebSecurity.GetUserId(User.Identity.Name));
ViewBag.ReturnUrl = Url.Action("Manage");
return View(l);
}
Again this works perfectly, however I only want to retrieve the user's information the once. Currently I can do what I want by calling it in the BeginExecuteCore, and then again in the controller as above. How can I call this the once to be used everywhere? i.e. populate the BaseViewModel.
Thanks for any help or pointers you may be able to give!
Ok. I've finally solved this.
I'm, creating a base model that all of my other view-models are going to inherit from. It can also be called directly in case any view doesn't require its own view-model.
public class ViewModelBase
{
public UserSettings ProfileSettings;
// Create a new instance, so we don't need to every time its called.
public ViewModelBase()
{
ProfileSettings = new UserSettings();
}
}
public class UserSettings // UserSettings is only used here and consumed by ViewModelBase, its the name there that is used throughout the application
{
public string BrandName { get; set; }
public UserRegistrationInformation UserSession;
}
This is being generated in the basecontroller.
public abstract class BaseController : Controller
{
public ViewModelBase vmb = new ViewModelBase();
protected override IAsyncResult BeginExecuteCore(AsyncCallback callback, object state)
{
string cultureName = null;
int userid = 0;
if (System.Web.Security.Membership.GetUser() != null)
{
//logged in
userid = (int)System.Web.Security.Membership.GetUser().ProviderUserKey;
WebUsersEntities db = new WebUsersEntities();
vmb.ProfileSettings.UserSession = db.UserRegistrationInformations.Where(r => r.uri_UserID == userid).FirstOrDefault();
vmb.ProfileSettings.BrandName = "test";
cultureName = "es-es";
}
else
{
// not logged in
cultureName = Request.UserLanguages[0]; // obtain it from HTTP header AcceptLanguages
}
// Validate culture name
cultureName = CultureHelper.GetImplementedCulture(cultureName); // This is safe
// Modify current thread's cultures
Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo(cultureName);
Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture;
return base.BeginExecuteCore(callback, state);
}
}
The other controllers all inherit from this controller. If any screen has a dedicated view-model it can retrieve the information from the model populated in the controller like this:
[AllowAnonymous]
public ActionResult Login(string returnUrl)
{
LoginModel v = new LoginModel();
v.ProfileSettings = vmb.ProfileSettings;
ViewBag.ReturnUrl = returnUrl;
return View(v);
}
I hope that helps someone in the future.
I am quite new to Unit testing and Mock concepts. I am trying to figure out how to write a good test case for the basic out-of-the box user registration code below:
[HttpPost]
public ActionResult Register(RegisterModel model)
{
if (ModelState.IsValid)
{
// Attempt to register the user
MembershipCreateStatus createStatus;
Membership.CreateUser(model.UserName, model.Password, model.Email, null, null, true, null, out createStatus);
if (createStatus == MembershipCreateStatus.Success)
{
FormsAuthentication.SetAuthCookie(model.UserName, false /* createPersistentCookie */);
return RedirectToAction("Index", "Home");
}
else
{
ModelState.AddModelError("", ErrorCodeToString(createStatus));
}
}
// If we got this far, something failed, redisplay form
return View(model);
}
Below are some of the specific points where I need your opinion/help:
I do not necessarily want to create a new user in ASP .Net membership database.
Based on the model passed in, how do I really make sure that if the user was successfully registered or there were errors in the process.
You have a problem with your code. Your action depends on a static method: Membership.CreateUser. And as you know static methods are PITAs to unit test.
So you could weaken the coupling by introducing a level of abstraction:
public interface IMyService
{
MembershipCreateStatus CreateUser(string username, string password, string email);
}
and then have some implementation that would use the current Membership provider:
public class MyService: IMyService
{
public MembershipCreateStatus CreateUser(string username, string password, string email)
{
MembershipCreateStatus status;
Membership.CreateUser(username, password, email, null, null, true, null, out status);
return status;
}
}
and finally the controller:
public class AccountController : Controller
{
private readonly IMyService _service;
public AccountController(IMyService service)
{
_service = service;
}
[HttpPost]
public ActionResult Register(RegisterModel model)
{
if (ModelState.IsValid)
{
// Attempt to register the user
var status = _service.CreateUser(model.UserName, model.Password, model.Email);
if (status == MembershipCreateStatus.Success)
{
FormsAuthentication.SetAuthCookie(model.UserName, false /* createPersistentCookie */);
return RedirectToAction("Index", "Home");
}
else
{
ModelState.AddModelError("", ErrorCodeToString(createStatus));
}
}
// If we got this far, something failed, redisplay form
return View(model);
}
}
OK, now that we have weakened the coupling we could use a mocking framework to mock the service in the unit test and make it trivial.
For example using Rhino Mocks you could create the following tests to cover the 2 failure cases:
[TestMethod]
public void Register_Action_Should_Redisplay_View_If_Model_Is_Invalid()
{
// arrange
var sut = new AccountController(null);
var model = new RegisterModel();
sut.ModelState.AddModelError("", "invalid email");
// act
var actual = sut.Register(model);
// assert
Assert.IsInstanceOfType(actual, typeof(ViewResult));
var viewResult = actual as ViewResult;
Assert.AreEqual(model, viewResult.Model);
}
[TestMethod]
public void Register_Action_Should_Redisplay_View_And_Add_Model_Error_If_Creation_Fails()
{
// arrange
var service = MockRepository.GenerateStub<IMyService>();
service
.Stub(x => x.CreateUser(null, null, null))
.IgnoreArguments()
.Return(MembershipCreateStatus.InvalidEmail);
var sut = new AccountController(service);
var model = new RegisterModel();
// act
var actual = sut.Register(model);
// assert
Assert.IsInstanceOfType(actual, typeof(ViewResult));
var viewResult = actual as ViewResult;
Assert.AreEqual(model, viewResult.Model);
Assert.IsFalse(sut.ModelState.IsValid);
}
The final test is the success case. We still have an issue with it. The issue is the following line:
FormsAuthentication.SetAuthCookie(model.UserName, false);
What is this? It is a static method call. So we proceed the same way as we did with the membership provider to weaken the coupling of our controller and the forms authentication system.
To test this method you can follow two way
Inside the test class create a new class that inherits from Membership class and override the method CreateUser.
Use Moq to mock the class.
For the first case I will check if username is equal to "GoodUser" or "BadUser" and generate a MembershipCreateStatus.Success or a different status.
For the second I will setup two method that follow the same idea as in the other method. See this link for an example
I've never used any Mock frameworks and actually new to ASP.NET MVC, testing and all this related stuff.
I'm trying to figure out how to use Moq framework for testing, but can't make it work. that's what I have at the moment: My repository interface:
public interface IUserRepository {
string GetUserEmail();
bool UserIsLoggedIn();
ViewModels.User CurrentUser();
void SaveUserToDb(ViewModels.RegisterUser viewUser);
bool LogOff();
bool LogOn(LogOnModel model);
bool ChangePassword(ChangePasswordModel model);
}
My Controller constuctor, I'm using Ninject for injection, it works fine
private readonly IUserRepository _userRepository;
public HomeController(IUserRepository userRepository) {
_userRepository = userRepository;
}
Simplest method in controller:
public ActionResult Index() {
ViewBag.UserEmail = _userRepository.GetUserEmail();
return View();
}
And my test method:
[TestMethod]
public void Index_Action_Test() {
// Arrange
string email = "test#test.com";
var rep = new Mock<IUserRepository>();
rep.Setup(r => r.GetUserEmail()).Returns(email);
var controller = new HomeController(rep.Object);
// Act
string result = controller.ViewBag.UserEmail;
// Assert
Assert.AreEqual(email, result);
}
I assume that this test must pass, but it fails with message Assert.AreEqual failed. Expected:<test#test.com>. Actual:<(null)>.
What am I doing wrong?
Thanks
Simple - you do not do Act part correctly. Fisrt you should call Index() action of the controller, and then Assert ViewBag.UserEmail correctness
// Act
controller.Index();
string result = controller.ViewBag.UserEmail;
By the way, advice - Using ViewBag is not the good practice. Define ViewModels instead