Why does not specifying view name cause my unit test to fail? - c#

In my unit tests, I find that when I return from a controller action using View() with no view name, ViewResult.ViewName is set to string.Empty. In order for this to be set, it has to be specified as a parameter to the View() call. For example, given the following unit test:
[TextFixture]
public class MyControllerTests
{
[Test]
public void TestMyAction()
{
var controller = new MyController();
var result = controller.MyAction();
Assert.AreEqual("MyAction", result.ViewName);
}
}
The following action implementation will cause the unit test to fail:
public class MyController : Controller
{
public ActionResult MyAction()
{
return View();
}
}
whilst this one will pass:
public class MyController : Controller
{
public ActionResult MyAction()
{
return View("MyAction");
}
}
I'm using ASP.NET MVC 2 (pre-beta) on .NET 4.0. I'm not using anything .NET 4.0-specific, however. I find this behaviour odd because I had thought that the ViewName was one of the reliable properties that could be checked in the unit tests' assertions.

This is a well known "feature" of ASP.NET MVC. Microsoft has documented it since the first version...
When no explicit view name is specified, the framework is trying to find one based on conventions (in "Views\controllername\actionname" or "Shared\controllername\actionname"). ViewName is only relevant if you want to deviate from that convention. So your unit test makes false assumptions.
HTH.

Related

ASP.NET Core Web API : route by query parameter

I am coming from a heavy Java/Spring background and trying to transition some knowledge over to ASP.NET Core 6.
In Spring, on a RestController, I am able to route the request based on the presence of a query parameter.
So a HttpRequest with the uri: /students?firstName=Kevin can be routed to a different controller method than a HttpRequest with the uri: /students.
In ASP.NET Core 6, I am unable to determine if the equivalent is possible after working through some examples and reading the documentation for Web API.
Here is what I am trying to achieve, is this possible using two methods and routing configuration that will discern which controller method to invoke based on the query parameter?
[ApiController]
[Route("Students")]
public class StudentHomeProfileController : ControllerBase
{
[HttpGet] //Route here when no parameters provided
public async Task<ActionResult<IEnumerable<Student>>> GetStudentAsync()
{
/* Code omitted */
}
[HttpGet] //Route here when firstName query param provided
public async Task<ActionResult<IEnumerable<Student>>> SearchStudentAsync([FromQuery] string firstName)
{
/* Code omitted */
}
}
While filtering by query parameters does not come with ASP.NET Core out of the box, it's not too hard to supply this functionality on your own.
When it comes to extensibility, ASP.NET has some superpowers, one of them is IActionConstraint, which
Supports conditional logic to determine whether or not an associated action is valid to be selected for the given request. (Source)
Creating an annotation to filter for query parameters is as easy as
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
public class QueryParameterConstraintAttribute : Attribute, IActionConstraint
{
private readonly string _parameterName;
public QueryParameterConstraintAttribute(string parameterName)
{
this._parameterName = parameterName;
}
public bool Accept(ActionConstraintContext context)
{
return context.RouteContext.HttpContext.Request.Query.Keys.Contains(this._parameterName);
}
public int Order { get; }
}
All that's left is annotating your controller method with that constraint
[HttpGet] //Route here when firstName query param provided
[QueryParameterConstraint("firstName")]
public async Task<ActionResult<IEnumerable<Student>>> SearchStudentAsync([FromQuery] string firstName)
{
/* Code omitted */
}
In a quick test I was able to confirm that it seems to work as intended, even if you add multiple of those attributes for different query parameters (if all conditions match, the route is called).
(Please note, this was tested with .NET Core 2.1. Anyway, it shuold be pretty much the same with .NET 6)
I think you are looking for something like this, you need to specify the parameter in the "HttpGet" attribute
https://learn.microsoft.com/en-us/aspnet/core/mvc/controllers/routing?view=aspnetcore-6.0#attribute-routing-with-http-verb-attributes
[Route("api/[controller]")]
[ApiController]
public class Test2Controller : ControllerBase
{
[HttpGet] // GET /api/test2
public IActionResult ListProducts()
{
return ControllerContext.MyDisplayRouteInfo();
}
[HttpGet("{id}")] // GET /api/test2/xyz
public IActionResult GetProduct(string id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
[HttpGet("int/{id:int}")] // GET /api/test2/int/3
public IActionResult GetIntProduct(int id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
[HttpGet("int2/{id}")] // GET /api/test2/int2/3
public IActionResult GetInt2Product(int id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
}
You are trying to differentiate API calls using query params. this is not the way to do this. if you want to separate the calls you should probably use path params instead.
Read more about Routing in ASP.NET Core - https://learn.microsoft.com/en-us/aspnet/core/fundamentals/routing?view=aspnetcore-6.0

UnitTest is expected ModelState to be invalid but it isn't

I have a .NetCore MVC project and I'm trying to unit test my controller.
ViewModel (Note the [Required] attribute):
public class Bank : BaseObject
{
[Required]
[DisplayName("Bank")]
public string Name { get; set; }
}
Controller Action:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create([Bind("Name")] Bank bank)
{
if (ModelState.IsValid)
{
await _bankService.Insert(bank);
return RedirectToAction(nameof(Index));
}
return View(bank);
}
Unit Test:
[Test]
public async Task When_PostingCreateBankThatIsInvalid_ThenBankIsReturned()
{
var bank = new Bank
{
//nothing set = invalid state - this is what we want
};
var controller = new BanksController(null);
var response = await controller.Create(bank);
}
I was expected the line if (ModelState.IsValid) to return false as Name is required - then I was going to perform my Asserts based on that. But the result is true and so we try to insert a bank.
What am I doing wrong here? I've Googled but I can only find answers that don't relate to unit tests. I thought the ModelState upheld the [Required] attribute?
If I test using the UI I am unable to create a bank without a name - it never even reaches the controller (as expected).
According to the docs:
Model validation occurs prior to each controller action being invoked
so I believe the problem lies with how I'm creating the BanksController. Am I approaching this test in the wrong manner? I wonder if I should just be setting the ModelState to invalid in the test...?
Attributes are metadata that is only recognized by the framework at run time and not during a unit test as they are actually read by the model binder that when the application is running.
For the state to change you will either have to run an integration test where the necessary parts of the framework are available to update the model state,
or update the model state manually since model binding isn't running (though an integration test would be used to exercise model binding) in the controller so that the test behaves as expected when being exercised.
[Test]
public async Task When_PostingCreateBankThatIsInvalid_ThenBankIsReturned() {
//Arrange
var bank = new Bank
{
//nothing set = invalid state - this is what we want
};
var controller = new BanksController(null);
controller.ModelState.AddModelError("Name","Name required");
//Act
var response = await controller.Create(bank);
//Assert
response.Should().NotBeNull()
.And.BeOfType<ViewResult>();
var viewResult = response as ViewResult;
viewResult.Model.Should().Be(model);
}
Don't try to test model validation or model binding in your unit tests - just test your action method's behavior when confronted with a particular ModelState value.
Reference Test controller logic in ASP.NET Core

Unit Testing ViewResult in Asp.NET MVC

Even though there are couple of Posts on StackOverflow about Unit Testing Action Result in MVC, I have a specific Question ....
Here is my ActionResult in Controller:
public ActionResult Index()
{
return View(db.Products.ToList());
}
Every Item in Products has different attributes like Name,Photo,Quantity etc..
I wrote a testmethod for this method .It looks as follows :
private CartEntity db = new CartEntity();
[TestMethod]
public void Test_Index()
{
//Arrange
ProductsController prodController = new ProductsController();
ViewResult = prodController.Index();
}
What Should I compare in this case since there are no parameters are being passed into Index Action
Check out the ViewResult class, this can show you what else you could test.
What you need to do is mock your DbContext and supply it with data in the Products property (DbSet<>) as this is being called in your controller's action.
You can then test
The type being returned
The model on the ViewResult
The ViewName which should be empty or Index
Sample code
[TestMethod]
public void Test_Index()
{
//Arrange
ProductsController prodController = new ProductsController(); // you should mock your DbContext and pass that in
// Act
var result = prodController.Index() as ViewResult;
// Assert
Assert.IsNotNull(result);
Assert.IsNotNull(result.Model); // add additional checks on the Model
Assert.IsTrue(string.IsNullOrEmpty(result.ViewName) || result.ViewName == "Index");
}
If you need help mocking a DbContext there are existing frameworks and articles on this subject. Here is one from Microsoft titled Testing with a mocking framework. Ideally you should be injecting your dependencies (including DbContext instances) into the constructors of your Controller instances using a DI framework like AutoFac or Unity or NInject (the list goes on). This also makes unit testing much easier.

ModelState.IsValid does not validate model

My Model class is as follow :
public class PostInputViewModel
{
[Required]
[MinLength(1)]
[MaxLength(125)]
public string Title { get; set; }
[Required]
[AllowHtml]
[Display(Name="Content")]
public string Content { get; set; }
}
and controller is as follow :
[HttpPost]
public ActionResult Write(PostInputViewModel input)
{
if (!ModelState.IsValid)
return View(input);
var post = new Post
{
Title = input.Title,
Content = input.Content,
DateCreated = DateTime.Now,
DateModified = DateTime.MaxValue,
};
dbContext.Posts.Add(post);
dbContext.SaveChanges();
return RedirectToAction("Index", "Home");
}
When I run web application by clicking F5, and if I don't input title and content value, ModelState.IsValid is false, However if I test controller class with unit test case, ModelState.IsValid is always true. The test case is as follow :
[TestMethod]
public void write_should_validate_model()
{
var input = new PostInputViewModel();
input.Title = null;
input.Content = null;
var actionResult = controller.Write(input) as ViewResult;
Assert.IsFalse(actionResult.ViewData.ModelState.IsValid);
}
Am I missing something?
Thanks in advance.
If you want to have your controller try to validate the model, you can call the TryValidateModel method before your assert:
controller.TryValidateModel(input);
But I agree that you'd really only be testing the validation attributes. It might be OK, though; it would validate that your model has the expected attributes applied.
Validation of model doesn't happen in the Controller. It happens before the model is passed to the controller.
Notice the controller action is only 'testing' whether the model is valid. Who is validating the model in your test case? Nothing!
You can do the validation using Validator class of .NET however in that case you would be testing .NET validation. This is one of the common mistakes people make while writing unit tests. They test 3rd party code instead of their own.
If you really want to test that you have applied the correct validation attributes to the class then you can simply reflect your class and check for attributes on properties. This way you would be skipping the .NET validation layer and your test will only fail if you miss an attribute.
Validation actually happens before the Write method on your controller is called, which populates the ModelState property.
Your unit test isn't really testing the controller in my opinion (if that is in fact what you're trying to do).
A true controller test would look something like this:
[TestMethod]
public void write_should_validate_model()
{
controller.ModelState.AddModelError("Title", "Empty"); //These values don't really matter
var actionResult = controller.Write(new PostInputViewModel()) as ViewResult;
//Assert that the correct view was returned i.e. Not Home/Index
//Assert that a new post has not been added to your Mock Repository
}

Can we make overloaded controller method in ASP .NET MVC

I am developing a ASP .Net MVC project but i can't make overload of Index() even when i have defined other method with different no. of parameters & have given proper routing for it . But it doesnot seems to work. So i just want to ask can we make overloaded methods in controller or not?
Controller actions with the same name are possible on the same controller if they are called with different HTTP verbs. Example:
public ActionResult Index()
{
return View();
}
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Index(SomeModel model)
{
return View();
}
When you call GET /Controller/Index the first action will be invoked and when you call POST /Controller/Index the second action will be invoked.
To be more specific, you have to vary it by selection criteria (which might be a verbs change as Darin said, but could also be other selector attributes like NonAction or ActionName). For that matter, you could create your own ActionNameSelectorAttribute derivative to create custom logic indicating when a given method should be used over another.
Update: added code per request.
I am actually creating a sample ActionMethodSelectorAttribute, b/c I couldn't think of a good usecase for just testing on the name that's not already covered by the ActionNameAttribute. Principle is the same either way, though.
public class AllParamsRequiredAttribute : ActionMethodSelectorAttribute
{
public override bool IsValidForRequest(ControllerContext controllerContext, System.Reflection.MethodInfo methodInfo)
{
var paramList = methodInfo.GetParameters().Select(p => p.Name);
foreach (var p in paramList)
if (controllerContext.Controller.ValueProvider.GetValue(controllerContext, p) == null) return false;
return true;
}
}
Basically, this one just gets the names of the params on the action method that it flags, and tests to make sure that the controller's ValueProvider has at least an attempted value of the same name as each. This obviously only works for simple types and doesn't test to make sure the attempted value can cast properly or anyting; it's nowhere close to a production attribute. Just wanted to show that it's easy and really any logic you can return a bool from can be used.
This could be applied, then as follows:
[AllParamsRequired]
public ViewResult Index(int count){/*... your logic ... */}
public ViewResult Index() {/*... more of your logic ... */}
in this example,and default routing, the url mydomain.com/?count=5 will match the first one, and the url mydomain.com/ will match the second one.
Paul

Categories