Unit Testing ViewResult in Asp.NET MVC - c#

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.

Related

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

Mocking a controller to test ViewEngine inside an Area - nullreference and RouteData

I have an Area in my MVC site. This area has the typical Controller/Model/View setup.
As a controller I have the following code:
public class DocumentCreatorController : Controller
{
// GET: Templates/DocumentCreator
public ActionResult OfferTemplate(BaseDocumentViewModel data)
{
return this.Pdf(nameof(OfferTemplate), data, "File.pdf");
}
}
The method this.Pdf does a couple of stuff, but the interesting is it comes down to the ViewEngine call:
var viewResult = ViewEngines.Engines.FindPartialView(controllerContext, partialViewName);
Here I call the FindPartialView with a ControllerContext and a PartialViewName. My PartialViewName comes from the nameof(OfferTemplate) from the controller action OfferTemplate. I think the controllercontext is my challenge.
My challenge:
When I want to set this up in a unit test (using Moq), I have the following code based on pages such as Mocking The RouteData Class in System.Web.Routing for MVC applications and Mocking Asp.net-mvc Controller Context:
[TestMethod]
public void OfferTemplate()
{
var ctr = SetupControllerWithContext();
}
private static DocumentCreatorController SetupControllerWithContext()
{
var routeData = new RouteData();
routeData.Values.Add("controller", "DocumentCreatorController");
routeData.Values.Add("action", "OfferTemplate");
var request = new Mock<HttpRequestBase>();
request.Expect(r => r.HttpMethod).Returns("GET");
var mockHttpContext = new Mock<HttpContextBase>();
mockHttpContext.Expect(c => c.Request).Returns(request.Object);
var controllerContext = new ControllerContext(mockHttpContext.Object
, routeData, new Mock<ControllerBase>().Object);
DocumentCreatorController ctr = new DocumentCreatorController();
ctr.ControllerContext = controllerContext;
return ctr;
}
Which gives the following error:
Eesy.Websites.Api.Tests.Controllers.DocumentCreatorControllerTest.OfferTemplate
threw exception:
System.NullReferenceException: Object reference not set to an instance of an object.
This I don't understand.
My folder setup:
Debug image on ControllerContext on calling the FindPartialView:
Anyone have an idea?
Is it because I setup the RouteData wrong?
You are trying to mock and test framework code. Abstract that functionality out into code you control so you can test in isolation if needed.
Currently the action and by extension the controller is tightly coupled to external 3rd party dependencies. If the goal was to test the controller action flow in isolation then it is advised to abstract out the 3rd party PDF generation so that it can be mocked for easier testability.
public interface IDocumentService {
ActionResult ToPdf(Controller arg1, string arg2, object arg3, string arg4);
}
The controller would explicitly depend on this abstraction via constructor injection.
public class DocumentCreatorController : Controller {
private readonly IDocumentService render;
DocumentCreatorController(IDocumentService render) {
this.render = render;
}
// GET: Templates/DocumentCreator
public ActionResult OfferTemplate(BaseDocumentViewModel data) {
return render.ToPdf(this, nameof(OfferTemplate), data, "File.pdf");
}
}
So now to test the controller's pdf generation process you need only mock your abstraction.
[TestMethod]
public void OfferTemplate() {
//Arrange
var serviceMock = new Mock<IDocumentService>();
//...setup mock for use case
var controller = new DocumentCreatorController(serviceMock.Object);
var data = new BaseDocumentViewModel {
//...
};
//Act
var actual = controller.OfferTemplate(data);
//Assert
//...assert behavior
}
The actual implementation of the service would encapsulate the actual functionality and would be registered with the dependency injection container along with the abstraction.
To test the actual generation you would need to do an integration test which is another topic.

Why Controller.UpdateModel doesn't validate model with globalized validation attributes from a unit test?

I created an ASP.NET MVC 5 application, with the following model class using a globalized Required validation attribute on its Email property:
public class Person
{
[Required(ErrorMessageResourceType = typeof(Resources),
ErrorMessageResourceName = "EmptyEmailError")]
public string Email { get; set; }
}
The Create POST action method of the PersonController class is:
[HttpPost]
public ActionResult Create(FormCollection formData)
{
Person newPerson = new Person();
UpdateModel(newPerson, formData);
if (ModelState.IsValid) {
// code to create the new person
return View("Index", newPerson);
}
else {
return View();
}
}
And the test case for verifying that a person with an empty e-mail address is not created is:
[TestMethod]
void Create_DoesNotCreatePerson_WhenEmailIsEmpty()
{
// arrange
FormCollection person = new FormCollection() { { "Email", string.Empty } };
PersonController controller = new PersonController();
controller.ControllerContext = new ControllerContext();
// act
controller.Create(person);
// assert
Assert.IsFalse(controller.ModelState.IsValid);
}
With this code the normal execution works fine. However, the test doesn't pass, which means that UpdateModel is returning true and thus not validating the person model correctly. The TryUpdateModelmethod doesn't work too.
But if I remove the ErrorMessage arguments from the Required validation attribute in the Person class (i.e. leaving it just as [Required]), then the test passes.
So I don't know why UpdateModel doesn't validate the globalized Person model when I call the Create action method from the test case.
I'm happy to see you are writing unit tests for your code :)
First, why don't you just pass Person in the first place, do you really need FormCollection?
[TestMethod]
void Create_DoesNotCreatePerson_WhenEmailIsEmpty()
{
// arrange
FormCollection person = new FormCollection() { { "Email", string.Empty } };
PersonController controller = new PersonController();
controller.ControllerContext = new ControllerContext();
// third, you might need to add this (haven't tested this)
controller.ModelState.AddModelError("Email", "fakeError");
// act
controller.Create(person);
// assert
Assert.IsFalse(controller.ModelState.IsValid);
}
Fourth and most important. I dont' think you should test like this. You want to test the outcome for different scenarios. That means you want to test the return value for the method. When you say Assert.IsFalse(controller.ModelState.IsValid); you basically say you don't trust Microsoft's code and you want to test their code. I can assure you they have unit tests on their code and you do not need to test it again ;)
Instead, test what view is returned and what object is returned from the action result. Here is a complete article for how to unit test this -> https://msdn.microsoft.com/en-us/library/gg416511(VS.98).aspx (See "Creating a test for retrieving contacts"). Hope this helps!
EDIT (Answer to comments):
Sory for the long response time.
I removed to "second".
If you want to to test the real production code, you want to do integration tests, not unit tests. If unit tests is what you want I recommend you to follow my solution proposal, otherwise create integration tests. I don't know how to convince you more then I've tried so read more and from several sources and I think you will agree later on how to do unit testing in this case :)
Good luck!

How to compare viewmodel and dependency in unit testing?

Below is the code of my method :-
[HttpPost]
public ActionResult Index(productViewModel model)
{
if (model != null)
{
return PartialView("_ProductGrid", GetProduct(model));
}
else
{
return RedirectToAction("Index", "Product");
}
}
And Below is code for unit testing method (in C#,MVC) :-
[TestMethod]
public void Index_WithModel_PostTest()
{
//Arrange
ProductController controller = new ProductController();
var model = new productViewModel()
{
Name="product1",
Description="desc"
};
//Act
PartialViewResult actual = controller.Index(model) as PartialViewResult;
if (actual != null)
{
var viewmodel = (productViewModel)((ViewResultBase)(actual)).Model;
int matches = _productService.GetDeals("", model.Description).Count +
_productService.GetInsurance("", model.Description).Count +
_productService.GetCategory("", model.Description).Count;
//Assert
Assert.IsNotNull(actual);
Assert.IsInstanceOfType(actual, typeof(PartialViewResult));
Assert.IsInstanceOfType(viewmodel, typeof(productViewModel));
Assert.AreEqual("_ProductGrid", actual.ViewName);
Assert.AreEqual(matches, viewmodel.Products.Count());
}
}
you can see on the below part of the method is that i am fetching the Products from all of the 3 methods. But all of those 3 methods are the ProductService dependency.And i want to know that should I mock that condition ? Or i can do in some other way ? I want to Assert the count of matches variable and the actual.Product.Count.
i think the essence of unit testing is to take advantage of separation of concerns. U should use moq to create a mock data accessed by a created repository and call that repository in the controller and then pass it to the unit testing.the essence of separation of concerns and unit testing is to be able to test each layer separately.Browse on MOQ

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

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.

Categories