Cannot create instance of repository object in action filter MVC - asp.net-mvc

public interface IUserRepository : IBaseRepository<user>
{
user GetUser(int userId);
user Get(string Email);
}
public class UserRepository : BaseRepository<user>, IUserRepository
{
public UserRepository(IUnitOfWork unit) : base(unit)
{
}
public void Dispose()
{
throw new NotImplementedException();
}
public user GetUser(int userId)
{
return dbSet.Where(x => x.ID == userId).FirstOrDefault();
}
public user Get(string Email)
{
var obj = dbSet.Where(s => s.Email == Email).FirstOrDefault();
return obj;
}
}
And I am using the repository in my controller as below
public class AccountController : Controller
{
private readonly ApplicationUserManager UserManager;
private readonly ApplicationSignInManager SignInManager;
private readonly IAuthenticationManager AuthenticationManager;
private readonly IUnitOfWork uow;
private readonly UserRepository userrepo;
public AccountController(UserRepository _userrepo, ApplicationUserManager userManager, ApplicationSignInManager signInManager, IAuthenticationManager authenticationManager,IUnitOfWork _uow)
{
this.UserManager = userManager;
this.SignInManager = signInManager;
this.AuthenticationManager = authenticationManager;
this.uow = _uow;
userrepo = _userrepo;
}
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
{
if (!ModelState.IsValid )
{
var user = UserManager.FindByEmail(model.Email);
var result = await SignInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, shouldLockout: false);
if (result)
{
var myUser = userRepo.Get(user.Id);
if (myUser.SubscriptionStatus == 1)
{
return RedirectToAction("ChangePassword", "Manage", new { ReturnUrl = returnUrl });
}
else
{
return RedirectToAction("Index","Admin");
}
}
}
}
And this is my action filter
public class CheckFirstLoginAttribute : ActionFilterAttribute
{
private readonly ApplicationUserManager UserManager;
private readonly IUnitOfWork uow;
private readonly UserRepository userrepo;
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
base.OnActionExecuting(filterContext);
string uName = HttpContext.Current.User.Identity.Name;
if (!string.IsNullOrEmpty(uName))
{
//var user = UserManager.FindByEmailAsync(uName);
//The above & below lines are not creating instance of the UserManager & UserRepository object, it is always null
user cUser= userrepo.GetUserId(uName);
filterContext.Result = new RedirectToRouteResult(new RouteValueDictionary(new
{
controller = "Manage",
action = "ChangePassword"
}));
}
}
}
And I am using Unity for the dependency injection
public class UnityConfig
{
#region Unity Container
private static Lazy<IUnityContainer> container = new Lazy<IUnityContainer>(() =>
{
var container = new UnityContainer();
RegisterTypes(container);
return container;
});
public static IUnityContainer GetConfiguredContainer()
{
return container.Value;
}
#endregion
private static void RegisterTypes(IUnityContainer container)
{
container.RegisterType<MyDbContext>();
container.RegisterType<UserRepository>();
container.RegisterType<IUnitOfWork, UnitOfWork>();
container.RegisterType<ApplicationDbContext>();
container.RegisterType<ApplicationSignInManager>();
container.RegisterType<ApplicationUserManager>();
container.RegisterType<HomeController>();
container.RegisterType<AccessCodeController>();
container.RegisterType<AdminController>();
container.RegisterType<IAuthenticationManager>(
new InjectionFactory(c => HttpContext.Current.GetOwinContext().Authentication));
container.RegisterType<IUserStore<MyUser, int>, UserStore<MyUser, MyRole, int, MyUserLogin, MyUserRole, MyUserClaim>>(
new InjectionConstructor(typeof(ApplicationDbContext)));
}
}
How can I create the instance of the repository class and access the Get(string email) method. So that I can check the subscription status from the database.
I tried many ways and always failed to create the instance.
Kindly Help me.
Thanks
Tarak

public class CheckFirstLoginAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
base.OnActionExecuting(filterContext);
string uName = HttpContext.Current.User.Identity.Name;
if (!string.IsNullOrEmpty(uName))
{
//Here is how I could get the instance of the repository
//I had to register the repository type and then
//I resolved it to obtain the object of the repository and
//access the methods in the repository
var container = new UnityContainer();
container.RegisterType<IUnitOfWork, UnitOfWork>();
container.RegisterType<UserRepository>();
UserRepository repo = container.Resolve<UserRepository>();
user cUser = repo.GetUserId(uName);
if (cUser.SubscriptionStatus == 1)
{
filterContext.Result = new RedirectToRouteResult(new RouteValueDictionary(new
{
controller = "Manage",
action = "ChangePassword"
}));
}
}
}
}

Related

Some services are not able to be constructed Error while validating the service descriptor Lifetime: Scoped Unable to resolve service for type

my usermanager services is :
public class UserManagerService : IUserManagerService
{
private readonly UserManager<UserModel> userManager;
private readonly UserServiceHelper userServiceHelper;
public UserManagerService(UserManager<UserModel> _userManager)
{
this.userManager = _userManager;
userServiceHelper = new UserServiceHelper();
}
public async Task<bool> CreateUser(UserViewModel user)
{
try
{
var new_user = userServiceHelper.GetNewItems(user);
await userManager.CreateAsync(new_user);
return true;
}
catch(Exception e)
{
throw;
}
}
IUsermanagerServices interface is :
public interface IUsermanagerServices
{
Task<bool> CreateUser(UserViewModel user);
Task<bool> CreateUserBatch(List<UserViewModel> users);
Task<bool> DeleteUser(UserViewModel user);
}
And userapicontroller is :
[Route("api/[controller]")]
[ApiController]
public class UserApiController : ControllerBase
{
private readonly IUserManagerService userManager;
public UserApiController(IUserManagerService _userManager)
{
userManager = _userManager;
}
[HttpPost]
public async Task<IActionResult> Post([FromBody] List<UserViewModel> users)
{
try
{
var _result = await userManager.CreateUserBatch(users);
var _response = new ResponseModel<bool>()
{
Content = _result,
Message = "np"
};
return Ok(_response);
}
catch(Exception e)
{
var _response = new ResponseModel<bool>()
{
Content = false,
ExceptionMessage = e.Message,
HasError = true
}.ToString();
return Problem(_response);
}
}
service extension is :
public static void AddUserManagerServices(this IServiceCollection services)
{
services.AddScoped<IUserManagerService, UserManagerService>();
}
and program :
#region
builder.Services.AddUserManagerServices();
#endregion
var app = builder.Build();
in this last line i have exception
Some services are not able to be constructed (Error while validating the service
descriptor 'ServiceType: Lifetime: Scoped ImplementationType:
Unable to resolve service for type while attempting to activate)
how can i fix it ?

How to seed the Authentication db in ASP NET MVC

In which module do I do the seeding of the db? I want to add roles and users if they do not exist yet.
In NET Core I would use the startup file in this way:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
using (IServiceScope serviceScope = app.ApplicationServices.GetRequiredService<IServiceScopeFactory>().CreateScope())
{
var dbContext = serviceScope.ServiceProvider.GetService<ApplicationDbContext>();
var roleManager = serviceScope.ServiceProvider.GetService<RoleManager<IdentityRole>>();
var userManager = serviceScope.ServiceProvider.GetService<UserManager<User>>();
DbSeeder.Seed(Configuration, dbContext, roleManager, userManager);
}
}
and in my class DBSeeder:
public static class DbSeeder
{
public static void Seed(IConfiguration configuration,
ApplicationDbContext dbContext,
RoleManager<IdentityRole> roleManager,
UserManager<User> userManager)
{
if (!dbContext.Users.Any()) CreateUsers(configuration, dbContext, roleManager, userManager).GetAwaiter().GetResult();
}
private static async Task CreateUsers(IConfiguration configuration,
ApplicationDbContext dbContext,
RoleManager<IdentityRole> roleManager,
UserManager<User> userManager)
{
string role_Administrator = "Administrator";
string role_RegisteredUser = "RegisteredUser";
if (!await roleManager.RoleExistsAsync(role_Administrator))
{
await roleManager.CreateAsync(new IdentityRole(role_Administrator));
}
if (!await roleManager.RoleExistsAsync(role_RegisteredUser))
{
await roleManager.CreateAsync(new IdentityRole(role_RegisteredUser));
}
var user_Admin = new User()
{
SecurityStamp = Guid.NewGuid().ToString(),
UserName = "Admin",
Email = configuration["ContactUs"],
DisplayName = "Admin",
EmailConfirmed = true
};
if (await userManager.FindByNameAsync(user_Admin.UserName) == null)
{
await userManager.CreateAsync(user_Admin, "Pass4Admin");
await userManager.AddToRoleAsync(user_Admin, role_RegisteredUser);
await userManager.AddToRoleAsync(user_Admin, role_Administrator);
}
await dbContext.SaveChangesAsync();
}

How to mock ApplicationUserManager from AccountController in MVC5

I am trying to write Unit Test for Register Method at AccountController
I am using moq and what is the correct way to mock ApplicationUserManager, ApplicationRoleManager and ApplicationSignInManager from Unit Test.
public AccountController(ApplicationUserManager userManager, ApplicationRoleManager roleManager, ApplicationSignInManager signInManager)
{
UserManager = userManager;
RoleManager = roleManager;
SignInManager = signInManager;
}
public ApplicationUserManager UserManager
{
get
{
return _userManager ?? HttpContext.GetOwinContext().GetUserManager<ApplicationUserManager>();
}
private set
{
_userManager = value;
}
}
private ApplicationSignInManager _signInManager;
public ApplicationSignInManager SignInManager
{
get
{
return _signInManager ?? HttpContext.GetOwinContext().Get<ApplicationSignInManager>();
}
private set { _signInManager = value; }
}
private ApplicationRoleManager _roleManager;
public ApplicationRoleManager RoleManager
{
get
{
return _roleManager ?? HttpContext.GetOwinContext().Get<ApplicationRoleManager>();
}
set
{
_roleManager = value;
}
}
That is probably not exactly what you need, but take a look, maybe you'll get the idea.
AccountController.cs
[HttpGet]
[Route("register")]
[AllowAnonymous]
public ActionResult Register()
{
if (IsUserAuthenticated)
{
return RedirectToAction("Index", "Home");
}
return View();
}
public bool IsUserAuthenticated
{
get
{
return
System.Web.HttpContext.Current.User.Identity.IsAuthenticated;
}
}
AccountControllerTests.cs
[Test]
public void GET__Register_UserLoggedIn_RedirectsToHomeIndex()
{
// Arrange
HttpContext.Current = CreateHttpContext(userLoggedIn: true);
var userStore = new Mock<IUserStore<ApplicationUser>>();
var userManager = new Mock<ApplicationUserManager>(userStore.Object);
var authenticationManager = new Mock<IAuthenticationManager>();
var signInManager = new Mock<ApplicationSignInManager>(userManager.Object, authenticationManager.Object);
var accountController = new AccountController(
userManager.Object, signInManager.Object, authenticationManager.Object);
// Act
var result = accountController.Register();
// Assert
Assert.That(result, Is.TypeOf<RedirectToRouteResult>());
}
[Test]
public void GET__Register_UserLoggedOut_ReturnsView()
{
// Arrange
HttpContext.Current = CreateHttpContext(userLoggedIn: false);
var userStore = new Mock<IUserStore<ApplicationUser>>();
var userManager = new Mock<ApplicationUserManager>(userStore.Object);
var authenticationManager = new Mock<IAuthenticationManager>();
var signInManager = new Mock<ApplicationSignInManager>(userManager.Object, authenticationManager.Object);
var accountController = new AccountController(
userManager.Object, signInManager.Object, authenticationManager.Object);
// Act
var result = accountController.Register();
// Assert
Assert.That(result, Is.TypeOf<ViewResult>());
}
private static HttpContext CreateHttpContext(bool userLoggedIn)
{
var httpContext = new HttpContext(
new HttpRequest(string.Empty, "http://sample.com", string.Empty),
new HttpResponse(new StringWriter())
)
{
User = userLoggedIn
? new GenericPrincipal(new GenericIdentity("userName"), new string[0])
: new GenericPrincipal(new GenericIdentity(string.Empty), new string[0])
};
return httpContext;
}

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

Resources