I am setting the thread culture to the culture sent via a cookie on the HttpRequest in the initialize method of a base controller I have created. I now want to create a unit test for this functionality.
In the test I have created a mock HttpContext and added the Cookie. Using this and a routeData helper, I create a RequestContext. Then I create a new controller and call Execute on it passing in the RequestContext.
I first hit a problem regarding TempData and SessionState, which I have fixed by setting the TempData and creating an EmptyTempDataProvider on the controller.
Then I had a problem with the VirtualPathProviderViewengine, which I fixed by creating a VoidActionInvoker.
Is this the best way to test the initialize method? Any doing anything similar and willing to share some code?
Thanks,
Jon
namespace MyApp.UnitTests {
[TestClass]
public class ControllerTests {
[TestInitialize]
public void Setup() {
RouteTable.Routes.Clear();
MyApp.MvcApplication.RegisterRoutes(RouteTable.Routes);
}
[TestMethod]
public void Thread_culture_should_be_set_to_cookie_culture() {
// Arrange
var context = HttpMockHelper.FakeHttpContext();
context.Request.Cookies.Add(new HttpCookie(CultureService.CookieName, "nl-NL"));
var reqContext = new RequestContext(context, "~/Home/Landing".Route());
var controller = new HomeController {
TempData = new TempDataDictionary(),
TempDataProvider = new EmptyTempDataProvider(),
ActionInvoker = new VoidActionInvoker()
};
// Act
(controller as IController).Execute(reqContext);
// Assert
Assert.AreEqual("nl-NL", Thread.CurrentThread.CurrentCulture.Name);
}
}
internal class VoidActionInvoker : ControllerActionInvoker {
protected override ActionExecutedContext InvokeActionMethodWithFilters(System.Reflection.MethodInfo methodInfo, IDictionary<string, object> parameters, IList<IActionFilter> filters) {
return new ActionExecutedContext(this.ControllerContext, false, null);
}
protected override void InvokeActionResult(ActionResult actionResult) {
}
}
internal class EmptyTempDataProvider : ITempDataProvider {
public void SaveTempData(ControllerContext controllerContext, IDictionary<string, object> values) {
}
public IDictionary<string, object> LoadTempData(ControllerContext controllerContext) {
return new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
}
}
}
Sounds right to me, the initialize method takes a parameter for the requestcontext that requires an HttpContext, if I've got your post right you're mocking these objects and adding the values you want to test with and then ensuring that your method works correctly. Sounds exactly like what I would do. I wouldn't mind seeing some of the code though.
Edit: Your tests look good to me.
Related
Situation is this:
I can't find a way of getting the viewModel that was passed to the POST action method.
[HttpPost]
public ActionResult Edit(SomeCoolModel viewModel)
{
// Some Exception happens here during the action execution...
}
Inside the overridable OnException for the controller:
protected override void OnException(ExceptionContext filterContext)
{
...
filterContext.Result = new ViewResult
{
ViewName = filterContext.RouteData.Values["action"].ToString(),
TempData = filterContext.Controller.TempData,
ViewData = filterContext.Controller.ViewData
};
}
When debugging the code filterContext.Controller.ViewData is null since the exception occurred while the code was executing and no view was returned.
Anyways I see that filterContext.Controller.ViewData.ModelState is filled and has all the values that I need but I don't have the full ViewData => viewModel object available. :(
I want to return the same View with the posted data/ViewModel back to the user in a central point. Hope you get my drift.
Is there any other path I can follow to achieve the objective?
You could create a custom model binder that inherits from DefaultModelBinder and assign the model to TempData:
public class MyCustomerBinder : DefaultModelBinder
{
protected override void OnModelUpdated(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
base.OnModelUpdated(controllerContext, bindingContext);
controllerContext.Controller.TempData["model"] = bindingContext.Model;
}
}
and register it in Global.asax:
ModelBinders.Binders.DefaultBinder = new MyCustomerBinder();
then access it:
protected override void OnException(ExceptionContext filterContext)
{
var model = filterContext.Controller.TempData["model"];
...
}
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
I got a custom ModelBinder and i would like to get the action. Because i want to get the Attributes of the action using reflection, the action name is not enough.
my action method:
[MyAttribute]
public ActionResult Index([ModelBinder(typeof(MyModelBinder))] MyModel model)
{
}
and here a typically ModelBinder
public class MyModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
// here i would like to get the action method and his "MyAttribute"
}
}
any suggestions, other solutions ?
many thanks in advance
No, you cannot with 100% certainty get the current action from a model binder. The model binder is not coupled to the action, but to binding to a model. For example, you can call
TryUpdateMode(model)
In an filter before an action has been chosen. Also note that an action method might not even be a CLR method (see http://haacked.com/archive/2009/02/17/aspnetmvc-ironruby-with-filters.aspx) that can be reflected on.
I think the real question is, what exactly are you trying to accomplish and is this the right way? If you want information from the action to be passed to the model binder (heeding the advice that your model binder should degrade gracefully if the information isn't there), you should use an action filter to put the information in HttpContext.Items (or somewhere like that) and then have your binder retrieve it.
An action filter's OnActionExecuting method receives an ActionExecutingContext which has an ActionDescriptor. You can call GetCustomAttributes on that.
You could try this:
var actionName = controllerContext.RouteData.GetRequiredString("action");
var myAttribute = (MyAttribute) Attribute.GetCustomAttribute(controllerContext.Controller.GetMethod(actionName), typeof(MyAttribute));
You could override ControllerActionInvoker.FindAction() to get the action's attribute and store it in HttpContext.Current.Items as mentioned here, or extendedControllerContext.RequestContext, as follows:
public class MyControllerActionInvoker : ControllerActionInvoker
{
protected override ActionDescriptor FindAction(ControllerContext controllerContext, ControllerDescriptor controllerDescriptor, string actionName)
{
var action = base.FindAction(controllerContext, controllerDescriptor, actionName);
if (action != null)
{
var requestContext = ExtendedRequestContext.Bind(controllerContext);
var attr = action.GetCustomAttributes(typeof(MyAttribute), false).FirstOrDefault();
if (attr != null)
requestContext.CustomAttribute = (MyAttribute)attr;
}
return action;
}
}
public class ExtendedRequestContext : RequestContext
{
public MyAttribute CustomAttribute { get; set; }
public static ExtendedRequestContext Bind(ControllerContext controllerContext)
{
var requestContext = new ExtendedRequestContext
{
HttpContext = controllerContext.RequestContext.HttpContext,
RouteData = controllerContext.RequestContext.RouteData
};
controllerContext.RequestContext = requestContext;
return requestContext;
}
}
The default action invoker is replaced either in your controller's constructor or in a custom controllers factory:
public MyController() : base()
{
ActionInvoker = new MyControllerActionInvoker();
}
By the way, Controller.TempData already contains an item of ReflectedParameterDescriptor type, which gives you access to ActionDescriptor, so the above code may be redundant. However, beware this is implementation specific, so may change over time.
Finally, get the attribute from that storage in your binder class:
var requestContext = (ExtendedRequestContext)controllerContext.RequestContext;
if (requestContext.CustomAttribute != null)
{
// apply your logic here
}
I have an action method like this in my controller
public ActionResult Index()
{
using (NorthwindDataContext db = new NorthwindDatacontext())
{
var results = db.GetRecordSets(arg1, ....).ToList();
// use results as list
}
return View();
}
and I wanted to start making tests for it (yes, after it was built, not before... but the code was written before I started to use TDD so... )
and I figured out that adding a property such as this one to the controller
public delegate NorthwindDatacontext ContextBuilderDelegate();
public ContextBuilderDelegate ContextBuilder { get; set; }
I could add in the constructor something like this...
ContextBuilder = () => new NorthwindDatacontext();
then I could test the ActionMethod setting the ContextBuilder property with a mock of NorthwindDatacontext
var controller = new MyController();
var mockDataContext = new Mock<NorthwindDatacontext>();
controller.ContextBuilder = () => mockDataContext.Object;
But... I found no way to use this because all methods of NorthwindDatacontext use ISingleResult as returnType and I cant find the way to create an object with that interface.
I've tried this
var theResult = new List<GetRecordSetsResult>();
// fill the data structure here with the provided result...
mockDataContext.Setup(c => c. GetRecordSets()).Returns(theResult as
ISingleResult<GetRecordSetsResult>);
but it doesn't work because theResult is null when converted to ISingleResult.
Is there any way to create a ISingleResult object to test this way or I'm doing the incorrect way to do things here?
Thanks in Advance
ToList() is an extension method for IEnumerable, which is easy to mock, because it only has one member method -- GetEnumerator().
Still you might have problems mocking NorthwindDataContext class, if its methods are not virtual...
Anyways, that's how I solved a similar problem in my sandbox, hope it helps:
public class MyType
{
public virtual ISingleResult<int> ReturnSomeResult() { throw new NotImplementedException(); }
}
[TestMethod]
public void TestMethod1()
{
var mockMyType = new Mock<MyType>();
var mockSingleResult = new Mock<ISingleResult<int>>();
IEnumerable<int> someEnumerable = new int[] {1,2,3,4,5};
mockSingleResult.Setup(result => result.GetEnumerator()).Returns(someEnumerable.GetEnumerator());
mockMyType.Setup(myType => myType.ReturnSomeResult()).Returns(mockSingleResult.Object);
Assert.AreEqual(15, mockMyType.Object.ReturnSomeResult().ToList().Sum());
}
I created a class that implemented ISingleResult and just put a List in it. I am fairly new to this type of coding, so while this worked for me, use at your own risk (and if you see holes post a comment).
class SingleResult<T>:ISingleResult<T>
{
readonly List<T> _list = new List<T>();
public void Add(T item)
{
_list.Add(item);
}
#region Interface Items
public IEnumerator<T> GetEnumerator()
{
return _list.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public object ReturnValue { get { return _list; } }
public void Dispose() { }
#endregion
}
This can then be used to return in part of a mock. This is how I ended up using it with Rhino Mocks:
[TestMethod]
public void TestSomething()
{
//Arrange
// Make a data context and DAL
var _ctx = MockRepository.GenerateMock<IDataClassesDataContext>();
var someDALClass = new SomeDALClass(_ctx);
User testUser = UserObjectHelper.TestUser();
SingleResult<User> userList = new SingleResult<User> { testUser };
// Indicate that we expect a call the to sproc GetUserByUserID
_ctx.Expect(x => x.GetUserByUserID(testUser.UserID)).Return(userList);
//Act
someDALClass.UpdateUser(testUser);
//Assert
Assert.IsTrue(SomeTestCondition());
}
I want to extend the WebFormViewEngine so that I can perform some post-processing - I want it to do it's stuff, then hand me the Html back, so I can do put some final touches to it. It needs to be done as a View/ViewEngine because I need access to the ViewData.
Unfortunately there seems to be no way to get the Html back from the WebFormView, and no way to hand a custom HtmlTextWriter to the WebFormView or ViewPage.
Surely there's a way to do this? No?
Littlecharva
You can use Action Filters to do this. Check out this tutorial at asp.net/mvc. You want to use a ResultsFilter.
As an alternate, you can override the virtual OnResultExecuted method of the Controller.
You can capture output recevier before the View gets rendered by overriding Render method of the WebFormView class. The trick is that the output receiver is not the System.IO.TextWriter writer but the Writer property of the viewContext.
Also, you have to extend WebFormViewEngine to return your views.
public class MyViewEngine : WebFormViewEngine
{
protected override IView CreatePartialView(ControllerContext controllerContext, string partialPath)
{
return new MyView(partialPath, null);
}
protected override IView CreateView(ControllerContext controllerContext, string viewPath, string masterPath)
{
return new MyView(viewPath, masterPath);
}
}
public class MyView : WebFormView
{
public MyView(string inViewPath, string inMasterPath) : base(inViewPath, inMasterPath) { }
public MyView(string inViewPath) : base(inViewPath) { }
public override void Render(ViewContext viewContext, System.IO.TextWriter writer)
{
//make a switch to custom output receiver
var oldWriter = viewContext.Writer;
viewContext.Writer = new System.IO.StringWriter();
base.Render(viewContext, null);
viewContext.Writer.Close();
//get output html
var html = ((System.IO.StringWriter)viewContext.Writer).GetStringBuilder();
//perform processing
html.Replace('a', 'b');
//retransmit output
viewContext.Writer = oldWriter;
viewContext.Writer.Write(html);
}
}
Okay, I have never done this before but I looked through reflector and the MVC assemblies. It appears as though you it might be possible to extend the ViewPage and the ViewPage and the ViewMasterPage object with your object. The in your own object you can override the render method and get a handle tot the HtmlTextWriter. Then just pass it on to the base and let it do it's thing. Something like this (this is un-tested and is only theoretical, there may be more methods you need to override.) I recommend using reflector to see how it is done now and even how other view engines like Spark do it.
public class MyPage : ViewPage
{
protected override void Render(System.Web.UI.HtmlTextWriter writer)
{
//Do custom stuff here
base.Render(writer);
}
}
public class MyPage<TModel> : MyPage where TModel : class
{
}