Building a MVC2 Template, Part 6, Writing Specs Continued
Apr 19
.Net, Development Asp.Net Mvc, MSpec, Nehemiah Project No Comments
Summary
In this post we’ll finish converting the auto-generated tests to MSpec specification.
Automate Running MSpec
![]()
If you wish to automate the running of the MSpec tool, open the properties to the Nehemiah.Specs project. Add the line below to the “Post-build event command line:” field. Now when Nehemiah.Specs builds successfully, MSpec will run automatically.
C:\_CodeVault\MSpec\mspec.exe $(TargetName)$(TargetExt) –html “$(ProjectDir)Report.html”
Converting Tests to Specs
I am writing these posts a couple of week ahead of the publish date. So hopefully I can smooth out all the bumps before they get published. However, that is not always the case. I have noticed the file TextExtensions.cs should have been named TestExtensions.cs. So unless someone points it out before this post is published, it will be known now.
I want to thank James Broome at http://jamesbroo.me/ for his great posts on Mvc controllers and BDD. Also the code below. I have renamed the file TextExtensions.cs to ActionResultExtensions.cs and added the full source.
using System.Web.Mvc;
namespace Nehemiah.Specs
{
public static class ActionResultExtensions
{
public static ViewResult is_a_view_and(this ActionResult result)
{
return (result as ViewResult);
}
public static RedirectResult is_a_redirect_and(this ActionResult result)
{
return (result as RedirectResult);
}
public static RedirectToRouteResult is_a_redirect_to_route_and(this ActionResult result)
{
return (result as RedirectToRouteResult);
}
public static string controller_name(this RedirectToRouteResult redirect_result)
{
return redirect_result.RouteValues["Controller"].ToString();
}
public static string action_name(this RedirectToRouteResult redirect_result)
{
return redirect_result.RouteValues["Action"].ToString();
}
public static void should_be_empty(this string the_string)
{
the_string.Equals(string.Empty);
}
} // End Class
} // End Namespace
Our newly converted specs look like this.
using System.Security.Principal;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
using System.Web.Security;
using Machine.Specifications;
using Nehemiah.Controllers;
using Nehemiah.Models;
namespace Nehemiah.Specs.Controllers
{
#region - Change Password Specs -
[Subject("Change Password")]
public class when_the_change_password_page_is_requested : AccountControllerContext
{
static ActionResult result;
static string key;
static int passwordLength;
Establish context = () =>
{
key = "PasswordLength";
passwordLength = 10;
};
Because of = () =>
{
result = controller.ChangePassword();
};
It should_display_the_change_password_page = () =>
{
result.is_a_view_and().ViewName.ShouldBeEmpty();
};
It should_include_a_value_for_a_minimum_password_length = () =>
{
result.is_a_view_and().ViewData[key].ShouldEqual(passwordLength);
};
}
[Subject("Change Password")]
public class change_password_displays_change_password_success_page_on_success : AccountControllerContext
{
static ActionResult result;
static ChangePasswordModel model;
Establish context = () =>
{
model = new ChangePasswordModel()
{
OldPassword = "goodOldPassword",
NewPassword = "goodNewPassword",
ConfirmPassword = "goodNewPassword"
};
};
Because of = () =>
{
result = controller.ChangePassword(model);
};
It should_return_a_RedirectToRouteResult = () =>
{
result.is_a_redirect_to_route_and().action_name().ShouldEqual("ChangePasswordSuccess");
};
}
[Subject("Change Password")]
public class change_password_displays_the_change_password_page_on_failure : AccountControllerContext
{
static ActionResult result;
static string key;
static int passwordLength;
static string failureMessage;
static ChangePasswordModel model;
Establish context = () =>
{
key = "PasswordLength";
passwordLength = 10;
failureMessage = "The current password is incorrect or the new password is invalid.";
model = new ChangePasswordModel()
{
OldPassword = "badOldPassword",
NewPassword = "goodNewPassword",
ConfirmPassword = "goodNewPassword"
};
};
Because of = () =>
{
result = controller.ChangePassword(model);
};
It should_display_the_change_password_page = () =>
{
result.is_a_view_and().ViewName.ShouldBeEmpty();
};
It should_include_a_change_password_failure_message = () =>
{
controller.ModelState[""].Errors[0].ErrorMessage.ShouldEqual(failureMessage);
controller.ModelState.IsValid.ShouldBeFalse();
};
It should_include_a_value_for_a_minimum_password_length = () =>
{
result.is_a_view_and().ViewData[key].ShouldEqual(passwordLength);
};
}
[Subject("Change Password")]
public class change_password_displays_the_change_password_page_on_data_validation_errors : AccountControllerContext
{
static ActionResult result;
static string key;
static int passwordLength;
static ChangePasswordModel model;
Establish context = () =>
{
key = "PasswordLength";
passwordLength = 10;
model = new ChangePasswordModel()
{
OldPassword = "badOldPassword",
NewPassword = "goodNewPassword",
ConfirmPassword = "goodNewPassword"
};
};
Because of = () =>
{
result = controller.ChangePassword(model);
controller.ModelState.AddModelError("", "Dummy error message.");
};
It should_display_the_change_password_page = () =>
{
result.is_a_view_and().ViewName.ShouldBeEmpty();
};
It should_include_previous_form_data_with_validation_errors = () =>
{
result.is_a_view_and().ViewData.Model.Equals(model);
};
It should_include_a_value_for_a_minimum_password_length = () =>
{
result.is_a_view_and().ViewData[key].ShouldEqual(passwordLength);
};
}
[Subject("Change Password")]
public class change_password_displays_the_change_password_success_page_on_success : AccountControllerContext
{
static ActionResult result;
Because of = () =>
{
result = controller.ChangePassword();
};
It should_display_the_change_password_success_page = () =>
{
result.is_a_view_and().ViewName.ShouldBeEmpty();
};
}
#endregion
#region - Logon/Logoff Specs-
[Subject("Logoff")]
public class log_off_will_log_out_the_user_and_return_the_home_page : AccountControllerContext
{
static ActionResult result;
Because of = () =>
{
result = controller.LogOff();
};
It should_log_out_the_user = () =>
{
MockFormsAuthenticationService svc = controller.FormsService as MockFormsAuthenticationService;
svc.SignOut_WasCalled.ShouldBeTrue();
};
It should_return_the_home_page = () =>
{
result.is_a_redirect_to_route_and().controller_name().ShouldEqual("Home");
result.is_a_redirect_to_route_and().action_name().ShouldEqual("Index");
};
}
[Subject("Logon")]
public class when_the_logon_page_is_requested : AccountControllerContext
{
static ActionResult result;
Because of = () =>
{
result = controller.LogOn();
};
It should_return_the_logon_page = () =>
{
result.is_a_view_and().ViewName.ShouldBeEmpty();
};
}
[Subject("Logon")]
public class log_on_returns_redirect_on_success_without_a_return_url : AccountControllerContext
{
static ActionResult result;
static LogOnModel model;
Establish context = () =>
{
model = new LogOnModel()
{
UserName = "someUser",
Password = "goodPassword",
RememberMe = false
};
};
Because of = () =>
{
result = controller.LogOn(model, null);
};
It should_log_in_the_user = () =>
{
MockFormsAuthenticationService svc = controller.FormsService as MockFormsAuthenticationService;
svc.SignIn_WasCalled.ShouldBeTrue();
};
It should_return_the_home_page = () =>
{
result.is_a_redirect_to_route_and().controller_name().ShouldEqual("Home");
result.is_a_redirect_to_route_and().action_name().ShouldEqual("Index");
};
}
[Subject("Logon")]
public class log_on_returns_redirect_on_success_with_a_return_url : AccountControllerContext
{
static ActionResult result;
static LogOnModel model;
Establish context = () =>
{
model = new LogOnModel()
{
UserName = "someUser",
Password = "goodPassword",
RememberMe = false
};
};
Because of = () =>
{
result = controller.LogOn(model, "/MyPage");
};
It should_log_in_the_user = () =>
{
MockFormsAuthenticationService svc = controller.FormsService as MockFormsAuthenticationService;
svc.SignIn_WasCalled.ShouldBeTrue();
};
It should_return_the_requested_url = () =>
{
result.is_a_redirect_and().Url.ShouldEqual("/MyPage");
};
}
[Subject("Logon")]
public class log_on_returns_log_on_page_on_data_validation_errors : AccountControllerContext
{
static ActionResult result;
static LogOnModel model;
static string failureMessage;
Establish context = () =>
{
failureMessage = "The user name or password provided is incorrect.";
model = new LogOnModel()
{
UserName = "someUser",
Password = "",
RememberMe = false
};
};
Because of = () =>
{
result = controller.LogOn(model, null);
};
It should_return_the_logon_page = () =>
{
result.is_a_view_and().ViewName.ShouldBeEmpty();
};
It should_include_previous_form_data_with_validation_errors = () =>
{
controller.ModelState[""].Errors[0].ErrorMessage.ShouldEqual(failureMessage);
controller.ModelState.IsValid.ShouldBeFalse();
};
}
[Subject("Logon")]
public class log_on_returns_log_on_page_on_log_in_failure : AccountControllerContext
{
static ActionResult result;
static LogOnModel model;
static string failureMessage;
Establish context = () =>
{
failureMessage = "The user name or password provided is incorrect.";
model = new LogOnModel()
{
UserName = "someUser",
Password = "badPassword",
RememberMe = false
};
};
Because of = () =>
{
result = controller.LogOn(model, null);
};
It should_return_the_logon_page = () =>
{
result.is_a_view_and().ViewName.ShouldBeEmpty();
};
It should_include_previous_form_data = () =>
{
LogOnModel model = result.is_a_view_and().ViewData.Model as LogOnModel;
model.UserName.ShouldEqual("someUser");
};
It should_include_a_validation_error_message = () =>
{
controller.ModelState[""].Errors[0].ErrorMessage.ShouldEqual(failureMessage);
controller.ModelState.IsValid.ShouldBeFalse();
};
}
#endregion
#region - Registration Specs -
[Subject("User Registration")]
public class when_the_registration_page_is_requested : AccountControllerContext
{
static ActionResult result;
Because of = () =>
{
result = controller.Register();
};
It should_return_the_registration_page = () =>
{
result.is_a_view_and().ViewName.ShouldBeEmpty();
};
}
[Subject("User Registration")]
public class registration_page_returns_user_to_home_page_on_success : AccountControllerContext
{
static ActionResult result;
static RegisterModel model;
Establish context = () =>
{
model = new RegisterModel()
{
UserName = "someUser",
Email = "goodEmail",
Password = "goodPassword",
ConfirmPassword = "goodPassword"
};
};
Because of = () =>
{
result = controller.Register(model);
};
It should_return_the_home_page = () =>
{
result.is_a_redirect_to_route_and().controller_name().ShouldEqual("Home");
result.is_a_redirect_to_route_and().action_name().ShouldEqual("Index");
};
}
[Subject("User Registration")]
public class registration_page_returns_registration_page_if_registration_fails : AccountControllerContext
{
static ActionResult result;
static RegisterModel model;
static string key;
static int passwordLength;
static string failureMessage;
Establish context = () =>
{
model = new RegisterModel()
{
UserName = "duplicateUser",
Email = "goodEmail",
Password = "goodPassword",
ConfirmPassword = "goodPassword"
};
key = "PasswordLength";
passwordLength = 10;
failureMessage = "The current password is incorrect or the new password is invalid.";
};
Because of = () =>
{
controller.ModelState.AddModelError("", "The current password is incorrect or the new password is invalid.");
result = controller.Register(model);
};
It should_return_the_registration_page = () =>
{
result.is_a_view_and().ViewName.ShouldBeEmpty();
};
It should_include_previous_form_data = () =>
{
RegisterModel model = result.is_a_view_and().ViewData.Model as RegisterModel;
model.UserName.ShouldEqual("duplicateUser");
model.Email.ShouldEqual("goodEmail");
};
It should_include_a_validation_error_message = () =>
{
controller.ModelState[""].Errors[0].ErrorMessage.ShouldEqual(failureMessage);
controller.ModelState.IsValid.ShouldBeFalse();
};
It should_include_a_value_for_a_minimum_password_length = () =>
{
result.is_a_view_and().ViewData[key].ShouldEqual(passwordLength);
};
}
[Subject("User Registration")]
public class registration_page_returns_registration_page_on_validation_errors : AccountControllerContext
{
static ActionResult result;
static RegisterModel model;
static string key;
static int passwordLength;
static string failureMessage;
Establish context = () =>
{
model = new RegisterModel()
{
UserName = "someUser",
Email = "goodEmail",
Password = "goodPassword",
ConfirmPassword = "badPassword"
};
key = "PasswordLength";
passwordLength = 10;
failureMessage = "The password and confirmation password do not match.";
};
Because of = () =>
{
controller.ModelState.AddModelError("", "The password and confirmation password do not match.");
result = controller.Register(model);
};
It should_return_the_registration_page = () =>
{
result.is_a_view_and().ViewName.ShouldBeEmpty();
};
It should_include_previous_form_data_with_validation_errors = () =>
{
RegisterModel model = result.is_a_view_and().ViewData.Model as RegisterModel;
model.UserName.ShouldEqual("someUser");
model.Email.ShouldEqual("goodEmail");
controller.ModelState[""].Errors[0].ErrorMessage.ShouldEqual(failureMessage);
controller.ModelState.IsValid.ShouldBeFalse();
};
It should_include_a_value_for_a_minimum_password_length = () =>
{
result.is_a_view_and().ViewData[key].ShouldEqual(passwordLength);
};
}
#endregion
public abstract class AccountControllerContext
{
protected static AccountController controller;
Establish context = () =>
{
controller = new AccountController()
{
FormsService = new MockFormsAuthenticationService(),
MembershipService = new MockMembershipService()
};
controller.ControllerContext = new ControllerContext()
{
Controller = controller,
RequestContext = new RequestContext(new MockHttpContext(), new RouteData())
};
};
}
public class MockFormsAuthenticationService : IFormsAuthenticationService
{
public bool SignIn_WasCalled;
public bool SignOut_WasCalled;
public void SignIn(string userName, bool createPersistentCookie)
{
// verify that the arguments are what we expected
userName.ShouldEqual("someUser");
createPersistentCookie.ShouldBeFalse();
SignIn_WasCalled = true;
}
public void SignOut()
{
SignOut_WasCalled = true;
}
}
public class MockHttpContext : HttpContextBase
{
private readonly IPrincipal _user = new GenericPrincipal(new GenericIdentity("someUser"), null /* roles */);
public override IPrincipal User
{
get
{
return _user;
}
set
{
base.User = value;
}
}
}
public class MockMembershipService : IMembershipService
{
public int MinPasswordLength
{
get { return 10; }
}
public bool ValidateUser(string userName, string password)
{
return (userName == "someUser" && password == "goodPassword");
}
public MembershipCreateStatus CreateUser(string userName, string password, string email)
{
if (userName == "duplicateUser")
{
return MembershipCreateStatus.DuplicateUserName;
}
// verify that values are what we expected
password.ShouldEqual("goodPassword");
email.ShouldEqual("goodEmail");
return MembershipCreateStatus.Success;
}
public bool ChangePassword(string userName, string oldPassword, string newPassword)
{
return (userName == "someUser" && oldPassword == "goodOldPassword" && newPassword == "goodNewPassword");
}
}
} // End Namespace
Commit all changes to Subversion. Then open http://localhost:8080 and verify that TeamCity build our project without any errors.













Recent Comments