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.
Related
I have following implementation in my base controller from which I am deriving most of my controllers. It accounts for setting the page title, meta description and keywords for each page if the values are not set via decorator on the controller actions.
BaseController
public class BaseController : Controller
{
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
//Page title
var title = filterContext.ActionDescriptor.GetCustomAttributes(typeof(PageTitleAttribute), false);
if (title.Length == 1)
ViewBag.Title = ((PageTitleAttribute)(title[0])).Parameter;
else
ViewBag.Title = "My website title";
//Page keywords
var keywords = filterContext.ActionDescriptor.GetCustomAttributes(typeof(MetaKeywordsAttribute), false);
if (keywords.Length == 1)
ViewBag.MetaKeywords = ((MetaKeywordsAttribute)(keywords[0])).Parameter;
else
ViewBag.MetaKeywords = "targeted SEO keywords";
//Page description
var description = filterContext.ActionDescriptor.GetCustomAttributes(typeof(MetaDescriptionAttribute), false);
if (description.Length == 1)
ViewBag.MetaDescription = ((MetaDescriptionAttribute)(description[0])).Parameter;
else
ViewBag.MetaDescription = "My custom description";
base.OnActionExecuting(filterContext);
}
}
The custom attributes are fairly simple:
public class PageTitleAttribute : Attribute
{
private readonly string _parameter;
public PageTitleAttribute(string parameter)
{
_parameter = parameter;
}
public string Parameter { get { return _parameter; } }
}
public class MetaDescriptionAttribute : Attribute
{
private readonly string _parameter;
public MetaDescriptionAttribute(string parameter)
{
_parameter = parameter;
}
public string Parameter { get { return _parameter; } }
}
public class MetaKeywordsAttribute : Attribute
{
private readonly string _parameter;
public MetaKeywordsAttribute(string parameter)
{
_parameter = parameter;
}
public string Parameter { get { return _parameter; } }
}
And this is how make use of the attributes on appropirate action methods in controller:
public class HomeController : BaseController
{
[PageTitle("My new website")]
[MetaKeywords("Explicitly set keywords")]
[MetaDescription("description goes here")]
public ActionResult Index()
{
return View();
}
public ActionResult Error()
{
return View();
}
}
This all seems to work just fine. Now I would like to create unit test to validate that if the values are not set via attribute on action methods, the default values will be rendered as set from base controller. How can I do that? I have some tests written but I don't think they are targeting the filterContext on the basecontroller. Specifically I am looking for test for Error action method which does not have anything attribute value set. Just for reference this is what I have setup now:
[TestMethod]
public void Attribute_when_set_should_return_attribute_values()
{
var method = typeof(HomeController).GetMethod("Index");
var pageTitle = method.GetCustomAttributes(typeof(PageTitleAttribute), false)
.Cast<PageTitleAttribute>()
.SingleOrDefault();
var keywords = method.GetCustomAttributes(typeof(MetaKeywordsAttribute), false)
.Cast<MetaKeywordsAttribute>()
.SingleOrDefault();
var description = method.GetCustomAttributes(typeof(MetaDescriptionAttribute), false)
.Cast<MetaDescriptionAttribute>()
.SingleOrDefault();
Assert.IsNotNull(pageTitle);
Assert.IsNotNull(keywords);
Assert.IsNotNull(description);
Assert.AreEqual("My new website", pageTitle.Parameter);
Assert.AreEqual("Explicitly set keywords", keywords.Parameter);
Assert.AreEqual("description goes here", description.Parameter);
}
I am trying to test my Account controller by using Moq here is what i have done
Controller
private readonly IWebSecurity _webSecurity;
public AccountController(IWebSecurity webSecurity)
{
this._webSecurity = webSecurity;
}
public ActionResult Login(LoginModel model, string returnUrl)
{
if (ModelState.IsValid && _webSecurity.login(model))
{
return RedirectToLocal(returnUrl);
}
// If we got this far, something failed, redisplay form
ModelState.AddModelError("", "The user name or password provided is incorrect.");
return View(model);
}
private ActionResult RedirectToLocal(string returnUrl)
{
if (Url.IsLocalUrl(returnUrl))
{
return Redirect(returnUrl);
}
else
{
return RedirectToAction("Index", "Home");
}
}
IWebSecurity
public interface IWebSecurity
{
bool login(LoginModel model);
}
public class WebSecurity : IWebSecurity
{
public bool login(LoginModel model)
{
return WebMatrix.WebData.WebSecurity.Login(model.UserName, model.Password, model.RememberMe);
}
}
MyTestClass
[AfterScenario]
public void OnAfterScenario() {
mockRepository.VerifyAll();
}
LoginModel loginModel;
AccountController _controller;
#region Initializing Mock Repository
readonly Mock<IWebSecurity> mockRepository = new Mock<IWebSecurity>(MockBehavior.Loose);
ViewResult viewResult;
#endregion
[Given]
public void Given_Account_controller()
{
_controller = new AccountController(mockRepository.Object);
}
[When]
public void When_login_is_called_with_LoginModel(Table table)
{
loginModel = new LoginModel
{
UserName = table.Rows[0][1],
Password = table.Rows[1][1]
};
mockRepository.Setup(x => x.login(loginModel)).Returns(true);
viewResult = (ViewResult)_controller.Login(loginModel, "/");
}
[Then]
public void Then_it_should_validate_LoginModel()
{
Assert.IsTrue(_controller.ModelState.IsValid);
}
[Then]
public void Then_it_should_return_default_view()
{
Assert.AreEqual(viewResult.ViewName, "Index");
}
But my test is failing and its giving expection when if come to Url.IsLocal in Redirect to Local method . so i think here is should mock my httpcontextbase and httpcontextrequestbase .
But don't know how to mock that .
Thanks in advance
You should mock the HttpContext. I wrote this helper to do this kind of things
public static Mock<HttpContextBase> MockControllerContext(bool authenticated, bool isAjaxRequest)
{
var request = new Mock<HttpRequestBase>();
request.SetupGet(r => r.HttpMethod).Returns("GET");
request.SetupGet(r => r.IsAuthenticated).Returns(authenticated);
request.SetupGet(r => r.ApplicationPath).Returns("/");
request.SetupGet(r => r.ServerVariables).Returns((NameValueCollection)null);
request.SetupGet(r => r.Url).Returns(new Uri("http://localhost/app", UriKind.Absolute));
if (isAjaxRequest)
request.SetupGet(x => x.Headers).Returns(new System.Net.WebHeaderCollection { { "X-Requested-With", "XMLHttpRequest" } });
var server = new Mock<HttpServerUtilityBase>();
server.Setup(x => x.MapPath(It.IsAny<string>())).Returns(BasePath);
var response = new Mock<HttpResponseBase>();
response.Setup(r => r.ApplyAppPathModifier(It.IsAny<string>())).Returns((String url) => url);
var session = new MockHttpSession();
var mockHttpContext = new Mock<HttpContextBase>();
mockHttpContext.Setup(c => c.Request).Returns(request.Object);
mockHttpContext.Setup(c => c.Response).Returns(response.Object);
mockHttpContext.Setup(c => c.Server).Returns(server.Object);
mockHttpContext.Setup(x => x.Session).Returns(session);
return mockHttpContext;
}
public class MockHttpSession : HttpSessionStateBase
{
private readonly Dictionary<string, object> sessionStorage = new Dictionary<string, object>();
public override object this[string name]
{
get { return sessionStorage.ContainsKey(name) ? sessionStorage[name] : null; }
set { sessionStorage[name] = value; }
}
public override void Remove(string name)
{
sessionStorage.Remove(name);
}
}
and in a test method you use it like that
private AccountController GetController(bool authenticated)
{
var requestContext = new RequestContext(Evoltel.BeniRosa.Web.Frontend.Tests.Utilities.MockControllerContext(authenticated, false).Object, new RouteData());
var controller = new CofaniController(cofaniRepository.Object, categorieRepository.Object, emailService.Object, usersService.Object)
{
Url = new UrlHelper(requestContext)
};
controller.ControllerContext = new ControllerContext()
{
Controller = controller,
RequestContext = requestContext
};
return controller;
}
[Test]
public void LogOn_Post_ReturnsRedirectOnSuccess_WithoutReturnUrl()
{
AccountController controller = GetController(false);
var httpContext = Utilities.MockControllerContext(false, false).Object;
controller.ControllerContext = new ControllerContext(httpContext, new RouteData(), controller);
LogOnModel model = new LogOnModel()
{
UserName = "someUser",
Password = "goodPassword",
RememberMe = false
};
ActionResult result = controller.LogOn(model, null);
Assert.IsInstanceOf(typeof(RedirectToRouteResult), result);
RedirectToRouteResult redirectResult = (RedirectToRouteResult)result;
Assert.AreEqual("Home", redirectResult.RouteValues["controller"]);
Assert.AreEqual("Index", redirectResult.RouteValues["action"]);
}
Hope it helps
In this particular issue you can simply overwrite controller's Url property with mocked UrlHelper class.
For HttpContext mocking, it might be good to inject HttpContextBase to your controller and configure your DI container to serve the proper one for you. It would ease mocking it later for testing purposes. I believe Autofac has some automatic way to configure container for ASP.NET-related classes like HttpContextBase.
EDIT
It seems you can't mock UrlHelper with Moq, as #lazyberezovsky wrote - you can mock only interfaces and virtual methods. But it does not stop you from writing your own mocked object. That's true you need to mock HttpContext, as it's required by UrlHelper constructor (actually, it's required by RequestContext constructor, which is required by UrlHelper constructor)... Moreover, IsLocalUrl does not use anything from context, so you do not have to provide any additional setup.
Sample code would look like that:
Controller:
public ActionResult Foo(string url)
{
if (Url.IsLocalUrl(url))
{
return Redirect(url);
}
return RedirectToAction("Index", "Home");
}
Tests:
[TestClass]
public class HomeControllerTests
{
private Mock<HttpContextBase> _contextMock;
private UrlHelper _urlHelperMock;
public HomeControllerTests()
{
_contextMock = new Mock<HttpContextBase>();
_urlHelperMock = new UrlHelper(new RequestContext(_contextMock.Object, new RouteData()));
}
[TestMethod]
public void LocalUrlTest()
{
HomeController controller = new HomeController();
controller.Url = _urlHelperMock;
RedirectResult result = (RedirectResult)controller.Foo("/");
Assert.AreEqual("/", result.Url);
}
[TestMethod]
public void ExternalUrlTest()
{
HomeController controller = new HomeController();
controller.Url = _urlHelperMock;
RedirectToRouteResult result = (RedirectToRouteResult)controller.Foo("http://test.com/SomeUrl");
Assert.AreEqual("Index", result.RouteValues["action"]);
Assert.AreEqual("Home", result.RouteValues["controller"]);
}
}
i wrote a unit test for edit_get action
My controller action is
public class GroupController : Controller
{
private readonly IGroupService groupService;
public GroupController(IGroupService groupService)
{
this.groupService = groupService;
}
public ActionResult EditGroup(int id)
{
var group = groupService.GetGroup(id);
CreateGroupFormModel editGroup = Mapper.Map<Group, CreateGroupFormModel>(group);
if (group == null)
{
return HttpNotFound();
}
return View("_EditGroup", editGroup);
}
the controller actionis working fine.
but when i am writing my unit test it get failed
my test is
[Test]
public void Edit_Get_ReturnsView()
{
//Arrange
CreateGroupFormModel group = new CreateGroupFormModel()
{
GroupId = 2,
GroupName = "Test",
Description = "test" };
GroupController controller = new GroupController();
var fake = groupService.GetGroup(2);
groupRepository.Setup(x => x.GetById(2)).Returns(fake);
Mapper.CreateMap<CreateGroupFormModel, Group>().ForAllMembers(opt => opt.Ignore());
Mapper.AssertConfigurationIsValid();
ViewResult actual = controller.EditGroup(2) as ViewResult;
Assert.IsNotNull(actual, "View Result is null");
}
can any one help me. the test fails as
Expected Not Null
actual Null
In your controller action you are calling var group = groupService.GetGroup(id); and it is not very clear where this groupService is coming from. In your unit test you must mock it. For this to be possible your GroupController must take this dependency as constructor injection.
Also in your unit test you seem to have declared some group variable which is never used.
For example:
public class GroupController: Controller
{
private readonly IGroupService groupService;
public GroupController(IGroupService groupService)
{
this.groupService = groupService;
}
public ActionResult EditGroup(int id)
{
var group = this.groupService.GetGroup(id);
CreateGroupFormModel editGroup = Mapper.Map<Group, CreateGroupFormModel>(group);
if (group == null)
{
return HttpNotFound();
}
return View("_EditGroup", editGroup);
}
}
and now in your unit test you could mock this group service and provide expectations on the result of the GetGroup method:
[Test]
public void Edit_Get_ReturnsView()
{
// arrange
var group = new CreateGroupFormModel
{
GroupId = 2,
GroupName ="Test",
Description ="test"
};
var groupServiceMock = new Mock<IGroupService>();
groupServiceMock.Setup(x => x.GetGroup(group.GroupId)).Returns(group);
var sut = new GroupController(groupServiceMock.Object);
Mapper.CreateMap<CreateGroupFormModel, Group>().ForAllMembers(opt => opt.Ignore());
Mapper.AssertConfigurationIsValid();
// act
var actual = sut.EditGroup(group.GroupId) as ViewResult;
// assert
Assert.IsNotNull(actual);
Assert.IsInstanceOfType(typeof(ViewResult), actual);
}
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"]);
}
I want to unit test the following ASP.NET MVC controller Index action. What do I replace the actual parameter in the assert below (stubbed with ?).
using System.Web.Mvc;
namespace MvcApplication1.Controllers
{
public class StatusController : Controller
{
public ActionResult Index()
{
return Content("Hello World!");
}
}
}
[TestMethod]
public void TestMethod1()
{
// Arrange
var controller = CreateStatusController();
// Act
var result = controller.Index();
// Assert
Assert.AreEqual( "Hello World!.", ? );
}
use the "as" operator to make a nullable cast. Then simply check for a null result
[TestMethod]
public void TestMethod1()
{
// Arrange
var controller = CreateStatusController();
// Act
var result = controller.Index() as ContentResult;
// Assert
Assert.NotNull(result);
Assert.AreEqual( "Hello World!.", result.Content);
}
I like creating assertion helpers for this sort of thing. For instance, you might do something like:
public static class AssertActionResult {
public static void IsContentResult(ActionResult result, string contentToMatch) {
var contentResult = result as ContentResult;
Assert.NotNull(contentResult);
Assert.AreEqual(contentToMatch, contentResult.Content);
}
}
You'd then call this like:
[TestMethod]
public void TestMethod1()
{
var controller = CreateStatusController();
var result = controller.Index();
AssertActionResult.IsContentResult(result, "Hello World!");
}
I think this makes the tests so much easier to read and write.
You cant test that the result is not null, that you receive a ContentResult and compare the values:
[TestMethod]
public void TestMethod1()
{
// Arrange
var controller = CreateStatusController();
// Act
var result = controller.Index();
// Assert
Assert.NotNull(result);
Assert.IsAssignableFrom(typeof(ContentResult), result);
Assert.AreEqual( "Hello World!.", result.Content);
}
I apoligize if the Nunit asserts aren't welformed, but look at it as pseudo-code :)