I am writing a unit test to test an MVC controller method (CRUD operation). The controller method accepts the usernameIdentity from session for auditing purposes.
I am trying the following to setup the ShimHttpSessionStateBase:
[TestMethod()]
public void CategoryCreateTest()
{
using (ShimsContext.Create())
{
var controller = new WebcamsController();
var category = new DataModel.Webcams.Category() { Name = "TestCategory" };
var currentSessionWrapper = new HttpSessionStateWrapper(HttpContext.Current.Session);
var session = new System.Web.Fakes.ShimHttpSessionStateBase(currentSessionWrapper);
session.Instance.Add("usernameIdentity", "DOMAIN\\USERNAME");
controller.ControllerContext = new ControllerContext(controller);
var result = controller.CategoryCreate(category);
Assert.IsInstanceOfType(result, typeof(PartialViewResult));
}
}
I am not sure how to appropriately create the controllerContext so that the Session will be read in the controller method.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult CategoryCreate([Bind(Include = "ID,Name")] Category category)
{
try
{
if (category != null
&& Validation.ValidNonNullableString(Session["usernameIdentity"].ToString(), 1, 60) && ModelState.IsValid)
{
businessLayer.SetCategories(category, "Add", AuthenticationHandler.GetHostInternetProtocolAddress, Session["usernameIdentity"].ToString(), AuthenticationHandler.GetUserInternetProtocolAddress);
return PartialView("CategoryList", businessLayer.GetCategoriesByCategory(0, AuthenticationHandler.GetHostInternetProtocolAddress));
}
return jsonResultHandler.JsonResultMessage("Failure", Properties.Resources.GlobalAttributeCreateFailed);
}
catch
{
throw;
}
}
Related
I'm starting a new project from scratch. Currently I'm working through design of some elements and attempting to implement unit testing early on. The problem, the unit tests are failing due to what appears to be a null response from a controller action. In the Watch Window, actionResult displays "Internal error in the expression evaluator" and contentResult is null.
How do I fix this?
xunit 2.2.0.3545, Moq 4.7.63.0
Here is the unit test (xunit):
// Used in the unit tests
private List<City> cities = new List<City>
{
new City { City_Name = "Chicago", City_Code = 1 },
new City { City_Name = "New York", City_Code = 2 },
new City { City_Name = "Seattle", City_Code = 3 }
};
[Fact]
public async Task Get_AllCities_PropertiesArePopulated()
{
// Arrange
var mockService = new Mock<ICityService>();
mockService.Setup(x => x.GetCities()).ReturnsAsync(this.cities);
var controller = new CityController(mockService.Object);
// Act
IHttpActionResult actionResult = await controller.GetCities();
var contentResult = actionResult as OkNegotiatedContentResult<List<City>>;
foreach (var city in contentResult.Content)
{
// doesn't get here due to .Content being null.
}
}
Here is my controller:
public class CityController : ApiController
{
private readonly ICityService cityService;
public CityController(ICityService svc)
{
this.cityService = svc;
}
public async Task<IHttpActionResult> GetCities()
{
var cities = await this.cityService.GetCities();
if (!cities.Any())
{
return this.BadRequest();
}
return this.Ok(cities);
}
}
The service interface:
public interface ICityService
{
Task<IEnumerable<City>> GetCities();
}
GetCities return IEnumerable<City>
Task<IEnumerable<City>> GetCities();
but in the cast of the test you cast it to the wrong type using List<City>
var contentResult = actionResult as OkNegotiatedContentResult<List<City>>;
When you should be using IEnumerable<City>
var contentResult = actionResult as OkNegotiatedContentResult<IEnumerable<City>>;
All of these views don't even have a title. So I cannot do an
Assert.AreEqual("Error",Viewbag.Title);
How else can I test an error controller to ensure atleast 85% code is covered
public class ErrorController : Controller
{
public ActionResult Index()
{
return View();
}
public ActionResult NotFound()
{
return View();
}
public ActionResult BadRequest()
{
return View();
}
public ActionResult ServerError()
{
return View();
}
}
The best I could come up with was
public class ErrorControllerTests : BaseTestController
{
ErrorController ec = new ErrorController();
[TestMethod()]
public void IndexTest()
{
var actionResult = ec.Index() as ViewResult;
Assert.AreSame(ec.ViewData, actionResult.ViewData);
Assert.AreSame(ec.TempData, actionResult.TempData);
Assert.AreEqual(actionResult.ViewName,"");
}
[TestMethod()]
public void NotFoundTest()
{
var NotFoundTestResult = ec.NotFound() as ViewResult;
Assert.AreEqual(NotFoundTestResult.ViewName,"");
}
[TestMethod()]
public void BadRequestTest()
{
var badRequestTestResult = ec.BadRequest() as ViewResult;
Assert.AreEqual(badRequestTestResult.ViewName,"");
}
[TestMethod()]
public void ServerErrorTest()
{
var serverErrorTestResult = ec.ServerError() as ViewResult;
Assert.AreEqual(serverErrorTestResult.ViewName, "");
}
}
You basically trying to test the Framework which should have already been done by Microsoft who owns the framework. You should try to avoid testing code you don't own or control.
That said, here is a work around to test the ErrorController that tries to mimic framework functionality and work around the limitations when it comes to unit tests.
[TestClass]
public class ErrorControllerTests {
[TestMethod]
public void TestIndexView() {
//Arrange
var expectedViewName = GetViewNameFromExpression<ErrorController>(c => c.Index());
var controller = ArrangeErrorController(expectedViewName);
//Act
var result = controller.Index() as ViewResult;
//Assert
Assert.IsNotNull(result);
//Replicating Framework functionality
MockExecuteResult(controller, result);
Assert.AreEqual(expectedViewName, result.ViewName);
CollectionAssert.AreEquivalent(controller.ViewData.ToList(), result.ViewData.ToList());
CollectionAssert.AreEquivalent(controller.TempData.ToList(), result.TempData.ToList());
}
private static void MockExecuteResult(ErrorController controller, ViewResult result) {
try {
result.View = Mock.Of<IView>();
result.ExecuteResult(controller.ControllerContext);
} catch { }
}
private static ErrorController ArrangeErrorController(string actionName) {
var controllerName = "error";
var context = GetControllerContext(actionName, controllerName);
var controller = new ErrorController() {
ControllerContext = context
};
context.Controller = controller;
return controller;
}
private static ControllerContext GetControllerContext(string actionName, string controllerName) {
RouteData rd = new RouteData();
rd.Values["action"] = actionName;
rd.Values["controller"] = controllerName;
var mockHttpContext = new Mock<HttpContextBase>();
mockHttpContext.Setup(c => c.Session).Returns((HttpSessionStateBase)null);
mockHttpContext.Setup(_ => _.Response.Output).Returns(new StringWriter());
return new ControllerContext(mockHttpContext.Object, rd, new Mock<Controller>().Object);
}
private static string GetViewNameFromExpression<TController>(Expression<Action<TController>> action) {
if (action == null) {
throw new ArgumentNullException("action");
}
var type = typeof(TController);
bool isController = type != null
&& type.Name.EndsWith("Controller", StringComparison.OrdinalIgnoreCase)
&& !type.IsAbstract
&& typeof(IController).IsAssignableFrom(type);
if (!isController) {
throw new InvalidOperationException("Invalid controller.");
}
MethodCallExpression body = action.Body as MethodCallExpression;
if (body == null)
throw new InvalidOperationException("Expression must be a method call.");
if (body.Object != action.Parameters[0])
throw new InvalidOperationException("Method call must target lambda argument.");
string actionName = body.Method.Name;
return actionName;
}
}
You can test for example view name:
Assert.AreEqual("Index", result.ViewName);
But to do that, you need to specify view name in action return:
public class ErrorController : Controller
{
public ActionResult Index()
{
return View("Index");
}
public ActionResult NotFound()
{
return View("NotFound");
}
public ActionResult BadRequest()
{
return View("BadRequest");
}
public ActionResult ServerError()
{
return View("ServerError");
}
}
I'm using the following approach to upload files through ASP.NET Web API controllers.
[System.Web.Http.HttpPost]
public HttpResponseMessage UploadFile()
{
HttpResponseMessage response;
try
{
int id = 0;
int? qId = null;
if (int.TryParse(HttpContext.Current.Request.Form["id"], out id))
{
qId = id;
}
var file = HttpContext.Current.Request.Files[0];
int filePursuitId = bl.UploadFile(qId, file);
}
catch (Exception ex)
{
}
return response;
}
In my unit tests I've created an HTTPContext class manually before calling the UploadFile action:
var request = new HttpRequest("", "http://localhost", "");
var context = new HttpContext(request, new HttpResponse(new StringWriter()));
HttpContext.Current = context;
response = controller.UploadFile();
Unfortunately, I wasn't able to add custom values to the Form collection, since it's read-only. Also I couldn't change the Files collection.
Is there any way to add custom values to the Form and Files properties of the Request to add needed data (id and file content) during the unit test?
Use some mocking framework like Moq instead. Create a mock HttpRequestBase and mock HttpContextBase with whatever data you need and set them on the controller.
using Moq;
using NUnit.Framework;
using SharpTestsEx;
namespace StackOverflowExample.Moq
{
public class MyController : Controller
{
public string UploadFile()
{
return Request.Form["id"];
}
}
[TestFixture]
public class WebApiTests
{
[Test]
public void Should_return_form_data()
{
//arrange
var formData = new NameValueCollection {{"id", "test"}};
var request = new Mock<HttpRequestBase>();
request.SetupGet(r => r.Form).Returns(formData);
var context = new Mock<HttpContextBase>();
context.SetupGet(c => c.Request).Returns(request.Object);
var myController = new MyController();
myController.ControllerContext = new ControllerContext(context.Object, new RouteData(), myController);
//act
var result = myController.UploadFile();
//assert
result.Should().Be.EqualTo(formData["id"]);
}
}
}
Since you have no control over those classes why not wrap/abstract the functionality behind one do control
IRequestService request;
[HttpPost]
public HttpResponseMessage UploadFile() {
HttpResponseMessage response;
try {
int id = 0;
int? qId = null;
if (int.TryParse(request.GetFormValue("id"), out id)) {
qId = id;
}
var file = request.GetFile(0);
int filePursuitId = bl.UploadFile(qId, file);
} catch (Exception ex) {
//...
}
return response;
}
Where request is one of your custom defined types IRequestService
public interface IRequestService {
string GetFormValue(string key);
HttpPostedFileBase GetFile(int index);
//...other functionality you may need to abstract
}
and can be implemented like this to be injected into your controller
public class RequestService : IRequestService {
public string GetFormValue(string key) {
return HttpContext.Current.Request.Form[key];
}
public HttpPostedFileBase GetFile(int index) {
return new HttpPostedFileWrapper(HttpContext.Current.Request.Files[index]);
}
}
in your unit test
var requestMock = new Mock<IRequestService>();
//you then setup the mock to return your fake data
//...
//and then inject it into your controller
var controller = new MyController(requestMock.Object);
//Act
response = controller.UploadFile();
I have a problem testing a logon method using existing AccountController (made by MVC)...
I have this simple test method:
[TestMethod]
public void LogOnTest1() {
AccountController controller = new AccountController();
LogOnModel logonModel = new LogOnModel();
logonModel.UserName = "test";
logonModel.Password = "test1234";
if ( controller.MembershipService == null ) {
controller.MembershipService = new AccountMembershipService();
}
if ( controller.FormsService == null ) {
controller.FormsService = new FormsAuthenticationService();
}
var result = controller.LogOn( logonModel, "" ) as ViewResult;
Assert.AreEqual( "Index", result.ViewName );
}
and the method defined in AccountController:
[HttpPost]
public ActionResult LogOn( LogOnModel model, string returnUrl ) {
if ( ModelState.IsValid ) {
if ( MembershipService.ValidateUser( model.UserName, model.Password ) ) {
FormsService.SignIn( model.UserName, model.RememberMe );
if ( !string.IsNullOrEmpty( returnUrl ) ) {
return Redirect( returnUrl );
} else {
return RedirectToAction( "Index", "Home" );
}
} else {
ModelState.AddModelError( "", "The user name or password provided is incorrect." );
}
}
// If we got this far, something failed, redisplay form
return View( model );
}
The above method is not defined/modified by me. Just created by when create an asp.net mvc project.
The problem is at line
if ( MembershipService.ValidateUser( model.UserName,model.Password ) ) {
which returns always false though I provided correct login info.
Where is my mistake ?
First of all, do not use if statements and other conditional logic in your tests. Also do not use concrete classes in your tests. If this test will fail, how will you know why? What was broken - controller or AccountMembershipService? Use abstract dependencies, which could be mocked.
public class AccountController : Controller
{
private IMembershipService _membershipService;
private IFormsService _formsService;
public AccountController(IMembershipService membershipService,
IFormsService formsService)
{
_membershipService = membershipService;
_formsService = formsService;
}
public ActionResult LogOn(LogOnModel model, string returnUrl)
{
// implementation goes here
}
}
And your tests:
[Test]
public void ShouldNotAcceptInvalidUser()
{
// Arrange
Mock<IMembershipService> membership = new Mock<IMembershipService>();
membership.Setup(m => m.ValidateUser(It.IsAny<string>(), It.IsAny<string>()))
.Returns(false);
Mock<IFormsService> forms = new Mock<IFormsService>();
var logonModel = new LogOnModel() { UserName = "", Password = "" };
var controller = new AccountController(membership.Object, forms.Object);
// Act
var result = controller.LogOn(logonModel, "") as ViewResult;
// Assert
Assert.That(result.ViewName, Is.EqualTo("Index"));
Assert.False(controller.ModelState.IsValid);
Assert.That(controller.ModelState[""],
Is.EqualTo("The user name or password provided is incorrect."));
}
Interesting here is that you don't care which logon info you passed to controller. You mock response from membership service, which says logon was incorrect.
FEW TESTS MORE:
[Test]
public void ShouldRedisplayViewWhenModelIsNotValid()
{
// Arrange
Mock<IMembershipService> membership = new Mock<IMembershipService>();
Mock<IFormsService> forms = new Mock<IFormsService>();
var model = new LogOnModel() { UserName = "", Password = "" };
var controller = new AccountController(membership.Object, forms.Object);
controller.ModelState.AddModelError("key", "error message");
// Act
var result = controller.LogOn(model, "") as ViewResult;
// Assert
Assert.That(result.ViewName, Is.EqualTo("LogOn"));
}
[Test]
public void ShouldSignInAndRedirectToIndex()
{
// Arrange
Mock<IMembershipService> membership = new Mock<IMembershipService>();
membership.Setup(m => m.ValidateUser(It.IsAny<string>(), It.IsAny<string>()))
.Returns(true);
Mock<IFormsService> forms = new Mock<IFormsService>();
var model = new LogOnModel() { UserName = "", Password = "" };
var controller = new AccountController(membership.Object, forms.Object);
controller.ModelState.AddModelError("key", "error message");
// Act
var result = controller.LogOn(model, "") as ViewResult;
// Assert
forms.Verify(f => f.SignIn(model.UserName, model.RememberMe));
Assert.That(result.ViewName, Is.EqualTo("Index"));
}
Im currently trying to test an insert method which uses TryUpdateModel(). I am faking the controllercontext which is needed and although that works it does not seem to be posting the model I have setup.
Here is the method I am testing:
[AcceptVerbs(HttpVerbs.Post)]
[GridAction]
public ActionResult _SaveAjaxEditing(int? id)
{
if (id == null)
{
Product product = new Product();
if (TryUpdateModel(product))
{
//The model is valid - insert the product.
productsRepository.Insert(product);// AddToProducts(product);
}
}
else
{
var recordToUpdate = productsRepository.Products.First(m => m.ProductID == id);
TryUpdateModel(recordToUpdate);
}
productsRepository.Save();
return View(new GridModel(productsRepository.Products.ToList()));
}
And here is my current test:
[TestMethod]
public void HomeControllerInsert_ValidProduct_CallsInsertForProducts()
{
//Arrange
InitaliseRepository();
var httpContext = CustomMockHelpers.FakeHttpContext();
var context = new ControllerContext(new RequestContext(httpContext, new RouteData()), controller);
controller.ControllerContext = context;
//controller.ControllerContext = new ControllerContext();
var request = Mock.Get(controller.Request);
request.Setup(r => r.Form).Returns(delegate()
{
var prod = new NameValueCollection
{
{"ProductID", "9999"},
{"Name", "Product Test"},
{"Price", "1234"},
{"SubCatID", "2"}
};
return prod;
});
// Act: ... when the user tries to delete that product
controller._SaveAjaxEditing(null);
//Assert
_mockProductsRepository.Verify(x => x.Insert(It.IsAny<Product>()));
}
The method is being called but when it gets to TryUpdateModel() it seems it cannot pickup the posted object. Any pointers on where I am going wrong would be great.
Sorted it. Seems mocking the Httpcontext completly was overkill.
controller.ControllerContext = new ControllerContext();
var prod = new FormCollection
{
{"ProductID", "1"},
{"Name", "Product Test"},
{"Price", "1234"},
{"SubCatID", "2"}
};
controller.ValueProvider = prod.ToValueProvider();
This does the trick. It is now posted.