Software Gardening

.NET, web, community, architecture, practices

Month: June 2013

Unit Testing ASP.Net MVC: Part 8, Testing Ajax and JSON

This is Part 8 in a series on unit testing MVC. Part 1 and an index to all posts in this series is here.

Just when you think you’ve done a good job with your application, good unit tests, users are happy, they bring you a change. In our case, the change calls for adding a country field to the Customer Create form. When the user picks the country, the State/Province field should populate with the states or provinces from that country. This will require making an AJAX call to the controller to get the list of states then returning that data via JSON.

We’ll need to create a Countries table and add a Country column to the States table. Then modify the State entity and add a Country Entity and supporting Repository, etc. Don’t forget to add the binding for Ninject and change the CustomerController constructor to accept the mocked Country table. You’ll need to change the tests to handle it too. Oh yes, a Country dropdown in Create.cshtml is needed too. I’ll be right here while you do all that.

Now that you made these changes we need to add a method to the CustomerController to return the JSON data.

   1:  public ActionResult GetStateValues(int countryId)
   2:  {
   3:      List<State> states = stateRepository.States
   4:  .Where(s => s.CountryId == countryId)
   5:  .OrderBy(s => s.Name).ToList();
   6:      var fs = from s in states
   7:                  select new
   8:                      {
   9:                          Code = s.Code,
  10:                          Name = s.Name
  11:                      };
  12:      JsonResult retVal = new JsonResult();
  13:      retVal.JsonRequestBehavior = JsonRequestBehavior.AllowGet;
  14:      retVal.Data = fs.ToList();
  15:      return retVal;

 

We also need to add the javascript to the Customer Create.cshtml file to make the call

   1:  <script>
   2:      $(document).ready(function () {
   3:          $('#CountryId').change(function () {
   4:              $('#StateCode').html('');
   5:              $.getJSON('@Url.Action("GetStateValues")',
   6:  { countryId: $('#CountryId').val() }, function (data) {
   7:                  var items = "";
   8:                  $.each(data, function (i, item) {
   9:                      $("#StateCode").append('<option value="'
  10:  + item.Code + '">' +  item.Name + '</option>') ;
  11:                  });
  12:              });
  13:          });
  14:      });
  15:  </script>

Now compile and run the code. When you select a country, the javascript function is called. That function passes the selected country Id to the C# code in the controller. The states for that country are retrieved from the database and put into a JSON object, then return back to the page. The javascript code then loads the new items into the dropdown.

And finally, we’re at the test.. But it isn’t easy because the JSON result contains an anonymous type. We simply can’t call ToString(). In fact, if you do call result.Data.ToString(), the result will be System.Collections.Generic.List`1[<>f__AnonymousType4`2[System.String,System.String]]

Instead, we have to do what the .Net framework would do, serialize it. First, the UnitTests project needs a reference to the System.Web.Extensions assembly. Then add a using statement to System.Web.Script.Serialization.

   1:  [Test]
   2:  public void GetStateValues_Returns_ProperValues()
   3:  {
   4:      // Arrange
   5:      Mock<ICustomerRepository> mock = new Mock<ICustomerRepository>();
   6:      Mock<IStateRepository> stateMock = new Mock<IStateRepository>();
   7:      Mock<ICountryRepository> countryMock = new Mock<ICountryRepository>();
   8:  
   9:      stateMock.Setup(m => m.States).Returns(new State[]
  10:          {
  11:              new State {Id = 1, Code = "UT", Name = "Utah", CountryId = 2},
  12:              new State {Id = 1, Code = "CA", Name = "California", CountryId = 2},
  13:              new State {Id = 1, Code = "NY", Name = "New York", CountryId = 2},
  14:              new State {Id = 1, Code = "BC", Name = "British Colombia", CountryId = 1}
  15:          }.AsQueryable());
  16:  
  17:      CustomerController controller = new CustomerController(mock.Object,
  18:  stateMock.Object, countryMock.Object);
  19:  
  20:      // Act
  21:      JsonResult result = controller.GetStateValues(2) as JsonResult;
  22:      JavaScriptSerializer serializer = new JavaScriptSerializer();
  23:      string actual = serializer.Serialize(result.Data);
  24:  
  25:      // Assert
  26:      Assert.AreEqual(@"[{""Code"":""CA"",""Name"":""California""},
  27:  {""Code"":""NY"",""Name"":""New York""},
  28:  {""Code"":""UT"",""Name"":""Utah""}]", actual);
  29:  }

 

Line 21 gets the actual JsonResult from the controller. We pass a parameter of 2 to get the states in country 2. Line 22 instantiates the Serialize and then the result data is serialized in line 23. Lines 26-28 compare the expected data with what actually gets returned.

I have one more post to make in this series. In that I’ll summarize everything we’ve learned and address some nagging questions that you may have, some of what have been asked in the comments or direct emails to me.

Source: CraigBerntson

Unit Testing ASP.Net: Part 7, Testing Http Status Codes

This is Part 7 in a series on unit testing MVC. Part 1 and an index to all posts in this series is here.

In Part 6 if this series, I showed you how to handle two return types from the same method. I’ll continue that line with another method that returns multiple values. Here’ is the Delete method from the CustomerController.

   1:  public ActionResult Delete(int id = 0)
   2:  {
   3:      Customer customer = 
   4:          customerRepository.Customers.FirstOrDefault(c => c.Id == id);
   5:      if (customer == null)
   6:      {
   7:          return HttpNotFound();
   8:      }
   9:      return View(customer);
  10:  }

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

 

You know how to handle the return View(customer), but what about HttpNotFound()? This return error causes the browser to display a 404 error. While it may be better to display an appropriate message, this is how the default scaffolding code is generated and there may be times you want to cause a 404. So, let’s see how to handle this in a unit test.

   1:  [Test]
   2:  public void Delete_Invalid_Customer_Returns_Http_404()
   3:  {
   4:      // Arrange
   5:      Mock<ICustomerRepository> mock = new Mock<ICustomerRepository>();
   6:      Mock<IStateRepository> stateMock = new Mock<IStateRepository>();
   7:   
   8:      CustomerController controller = new CustomerController(mock.Object, 
   9:  stateMock.Object);
  10:      CustomerCreateEditViewModel viewModel = 
  11:  new CustomerCreateEditViewModel();
  12:              
  13:      // Act
  14:      var actual = controller.Delete(27) as HttpNotFoundResult;
  15:   
  16:      // Assert
  17:      Assert.AreEqual(404, actual.StatusCode);
  18:  }
 
 

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

Line 14 casts the return value to HttpNotFoundResult then in line 17 you Assert that the StatusCode value is 404. That’s it.

At this point, you should be able to write unit tests for all the methods in all three controllers in our example. But just as you get it done, you get a new requirement to add the country to the Customer Create and Edit pages. The user will first pick a country then the State/Province drop down will populate for the select country. This means you need to add a unit test to accept the Ajax call and test for a JSON response. I’ll show you how that’s done in Part 8.

Source: CraigBerntson

ASP.Net MVC Unit Testing: Part 6, The [HttpPost] Method

This is Part 6 in a series on unit testing MVC. Part 1 and an index to all posts in this series is here.

In the previous post in this series, I showed you how to get test SelectList objects. We replaced the model with a viewmodel and passed that to the View. However, we didn’t deal with actually saving the data. I also detoured from unit testing a bit to show an easy way to map a model to a view model. Now it’s time to get back to our topic and learn how to test is a method that has multiple return points. In our sample application, we’re going to test the [HttpPost] Create method.

   1:  [HttpPost]
   2:  [ValidateAntiForgeryToken]
   3:  public ActionResult Create(CustomerCreateEditViewModel viewModel)
   4:  {
   5:      if (ModelState.IsValid)
   6:      {
   7:          Customer customer = AutoMapper.Mapper.Map<CustomerCreateEditViewModel,
   8:  Customer>(viewModel);
   9:          customer = customerRepository.Save(customer);
  10:          return RedirectToAction("Index");
  11:      }
  12:  
  13:      return View(viewModel);
  14:  }

Let’s deal with the first return on line 10. In order to get to that return, ModelState.IsValid must be ture. But how do we ensure this? We’ll need to add some validation to the ViewModel. To keep it simple, I’ll just require a FirstName and LastName. Here’s the changed code snippet.

   1:  [Required]
   2:  [DisplayName("First name")]
   3:  public string FirstName { get; set; }
   4:  
   5:  [Required]
   6:  [DisplayName("Last name")]
   7:  public string LastName { get; set; }

 

Now we need to write a test. In an earlier post in this series, I changed the return value of the controller method from ActionResult to ViewResult. We can’t do that here because if the ViewState is Valid, it returns a RedirectToAction, not a View. So, we need to ensure that we’re getting a RedirectToAction. That’s our first test.

   1:  [Test]
   2:  public void Create_ViewState_Is_Valid_Returns_RedirectToRouteResult()
   3:  {
   4:      // Arrange
   5:      Mock<ICustomerRepository> mock = new Mock<ICustomerRepository>();
   6:      Mock<IStateRepository> stateMock = new Mock<IStateRepository>();
   7:  
   8:      CustomerController controller = new CustomerController(mock.Object,
   9:  stateMock.Object);
  10:      CustomerCreateEditViewModel viewModel =
  11:  new CustomerCreateEditViewModel();
  12:      viewModel.FirstName = "Fred";
  13:      viewModel.LastName = "Flintstone";
  14:      AutoMapper.Mapper.Reset();
  15:      AutoMapper.Mapper.CreateMap<CustomerCreateEditViewModel,
  16:  Customer>();
  17:  
  18:      // Act
  19:      var actual = controller.Create(viewModel);
  20:  
  21:      // Assert
  22:      Assert.IsInstanceOf<RedirectToRouteResult>(actual);
  23:  }



 

In lines 10-13 we setup the ViewModel so that it will be valid. Lines 14-16 setup the mapping needed so that AutoMapper will work. We then call the Create method of the controller and in line 19 assert the return value os of the proper type.

Next we need to verify that the route value that gets returned is correct. The Create method returns to the Index action so that’s what we need to test for.

   1:  [Test]
   2:  public void Create_ViewState_Is_Valid_Contains_Correct_Action()
   3:  {
   4:      // Arrange
   5:      Mock<ICustomerRepository> mock = new Mock<ICustomerRepository>();
   6:      Mock<IStateRepository> stateMock = new Mock<IStateRepository>();
   7:  
   8:      CustomerController controller = new CustomerController(mock.Object,
   9:  stateMock.Object);
  10:      CustomerCreateEditViewModel viewModel =
  11:  new CustomerCreateEditViewModel();
  12:      viewModel.FirstName = "Fred";
  13:      viewModel.LastName = "Flintstone";
  14:      AutoMapper.Mapper.Reset();
  15:      AutoMapper.Mapper.CreateMap<CustomerCreateEditViewModel,
  16:  Customer>();
  17:  
  18:      // Act
  19:      var actual = controller.Create(viewModel) as RedirectToRouteResult;
  20:  
  21:      // Assert
  22:      Assert.AreEqual("Index", actual.RouteValues["action"]);
  23:  }


 

This test is similar to the previous one except that we cast the return value to RedirectToRouteResult so that we can interrogate it further. Line 19 asserts that the action is Index. Once you get green on both tests, it’s time to handle the condition that the ViewState is false.

First, change the return statement of the [HttpPost] Create method so it contains the actual View we want. Otherwise, the ViewName property is empty.

   1:  return View("Create", viewModel);

 

For our tests, we can test two things. The first is that we get a ViewResult (which is the View).

   1:  [Test]
   2:  public void Create_ViewState_Is_Invalid_Returns_ViewResult()
   3:  {
   4:      // Arrange
   5:      Mock<ICustomerRepository> mock = new Mock<ICustomerRepository>();
   6:      Mock<IStateRepository> stateMock = new Mock<IStateRepository>();
   7:  
   8:      CustomerController controller = new CustomerController(mock.Object,
   9:  stateMock.Object);
  10:      CustomerCreateEditViewModel viewModel =
  11:  new CustomerCreateEditViewModel();
  12:      controller.ModelState.AddModelError("FirstName", "First name is required");
  13:  
  14:      // Act
  15:      var actual = controller.Create(viewModel) as ViewResult;
  16:  
  17:      // Assert
  18:      Assert.IsInstanceOf<ViewResult>(actual);
  19:  }

 

In line 12, we set an error condition on the ModelState so it won’t pass. The Assert in line 18 verifies we get a ViewResult.

Now for the second test, we need to make sure we get the Create View.

   1:  [Test]
   2:  public void Create_ViewState_Is_Invalid_Returns_Correct_View()
   3:  {
   4:      // Arrange
   5:      Mock<ICustomerRepository> mock = new Mock<ICustomerRepository>();
   6:      Mock<IStateRepository> stateMock = new Mock<IStateRepository>();
   7:  
   8:      CustomerController controller = new CustomerController(mock.Object,
   9:  stateMock.Object);
  10:      CustomerCreateEditViewModel viewModel =
  11:  new CustomerCreateEditViewModel();
  12:      controller.ModelState.AddModelError("FirstName", "First name is required");
  13:  
  14:      // Act
  15:      var actual = controller.Create(viewModel) as ViewResult;
  16:  
  17:      // Assert
  18:      Assert.AreEqual("Create", actual.ViewName);
  19:  }

Really, the only difference between these last two tests is the Assert.

That wraps up testing the Create method. It should be easy to write tests for the Edit methods as they are the same as Create. Now we turn our attention to the Delete method.

   1:  public ActionResult Delete(int id = 0)
   2:  {
   3:      Customer customer =
   4:          customerRepository.Customers.FirstOrDefault(c => c.Id == id);
   5:      if (customer == null)
   6:      {
   7:          return HttpNotFound();
   8:      }
   9:      return View(customer);
  10:  }

 

Note the if statement on lines 5-8. If the requested Customer row isn’t found, the method returns HttpNotFound which results in a 404 error page in the browser. How do we test for this? That, is the topic of the next part in this series.

Source: CraigBerntson

© 2017 Software Gardening

Theme by Anders NorenUp ↑