.netcore continuous integration testing unit test papers of the MVC layer

Earlier we talked about a lot of unit testing methods and techniques, whether in the .net core and .net framework which is common, but mvc project, there is a more special class Controller, Controller first class return results with the ordinary class does not like an ordinary class returns are determined by the type of ActionResult returned or core mvc mvc project's return IActionResult is a highly encapsulated object, I would like to be very careful of is not a test very easy thing to do. it is therefore recommended as far as possible when writing code business logic unit of code is written to a separate class, Controller in return only a simple front-end test and each http request parameters and status data. Another point is that Controller after the http request arrives dynamically created, unit testing, when many objects, such as Httpcontext, Modelstate, request, response, routedata, uri, MetadataProvider are all non-existent, and there is a great difference http request environment, but we still Controller unit testing through to do a lot of work to ensure that the result is what we want.

Action to ensure that returns the correct View and ViewModel

We use HomeController inside the Index method, slightly modified the code

public IActionResult Index()
        {
            return View("Index","hello");
        }

It test code as follows

        [Fact]
        public void ViewTest()
        {
            HomeController hc = new HomeController();
            var result = (ViewResult)hc.Index();
            var viewName = result.ViewName;
            var model = (string)result.Model;
            Assert.True(viewName == "Index" && model == "hello");
        }

First we create a Controller class, because the business we need this method returns a View, which is predictable in advance, so we hc.Index results into ViewResult, if the conversion fails then there is a bug in the program.

Below are respectively acquire View the name of the data model, then we assert View name is Index, value model is hello, of course, the above code is relatively simple obviously through, we would also like to Model in the actual business is more complex assertion.

It should be noted, Action returns of view not have a name, and if this method returns the corresponding view, the default name can be omitted, so that the above assertion will fail, so if you do not write the name of the time we can assert ViewName is empty, the process also returns the default view.

Action returns to ensure the correct viewData

We HomeController in the Index method then slightly change the next as follows:

 public IActionResult Index()
        {
            ViewBag.name = "sto";
            return View("Index","hello");
        }

Test methods are as follows

 HomeController hc = new HomeController();
            var name= result.ViewData["name"];
            Assert.True(name=="sto");

See above some of my colleagues may have doubts, why is ViewBag set ViewData and can get to it, many of them from the Internet had seen some people say is both a dynamic type, is a dictionary type, this is only their external performance, in fact, only those who are running the same object, so its value can be acquired through ViewData [xxx] way.

Ensure that the correct procedures to enter the branch

We often see the following code

 public IActionResult Index(Student stud)
        {
            if (!ModelState.IsValid) return BadRequest();
            return View("Index","hello");
        }

StudentWe like add comments, read as follows

 public class Student
    {
        public string Name { get; set; }
        [Range(3,10,ErrorMessage ="年龄必须在三到十岁之间")]
        public int Age { get; set; }
        public byte Gender { get; set; }
        public string School { get; set; }
    }

We comment on the age, identity it must be a value between 3-10.

We write the following test to test if the model binding if there is an error when the return BadRequest

        [Fact]
        public async Task ViewTest()
        {
            HomeController hc = new HomeController();
            var result = hc.Index(new Student{Age=1});
            Assert.IsType<BadRequestResult>(result);
        }

We tested the above stud age is set to 1, it is not between 3 and 10, and therefore should return BadRequest A (actually a type object BadRequestResult) The program logic, however, will find that the test run the above test did not pass through a single step we found that debugging is actually a return ViewResult object. Why is it so? it is actually quite simple, because Modelstate.IsValid is when model validation model binding if there is an error, it will Modelstate objects in writing, however, control is not dynamically created, the data model is not dynamic binding, and no error message is added to the Modelstate in action, so it starts to return the unit tests True, it is not no way to test it, is not in fact, because ModelState program not only can be dynamically added when model binding, we can also add their own business logic according to which the controller.

We put the code to read as follows

       [Fact]
        public async Task ViewTest()
        {
            HomeController hc = new HomeController();
            hc.ModelState.AddModelError("Age", "年龄不在3到10范围内");
            var result = hc.Index(new Student{Age=1});
             Assert.IsType<BadRequestResult>(result);
        }

Since we know that Age value here is illegal, and therefore explicitly explicitly written to an error in the controller's Modelstate objects, such Model.Isvalid should return False, logic should BadRequest years into the above test.

Ensure that the program be redirected to the correct Action

We Index method to read as follows

public IActionResult Index(int? id)
        {
            if (!id.HasValue) return RedirectToAction("Contact","Home");
            return View("Index","hello");
        }

If the id is null and they will return a RedirectToActionResult, leads to the next method in the Home Contact controller.

 [Fact]
        public async Task ViewTest()
        {
            HomeController hc = new HomeController();
            var result = hc.Index(null);
            var redirect = (RedirectToActionResult) result;
            var controllerName = redirect.ControllerName;
            var actionName = redirect.ActionName;
            Assert.True(controllerName == "Home" && actionName == "Contact");
        }

Of course, the above code is not very meaningful, because RediRectToAction which is often the argument passed two strings, does not require special complex calculations, and redirect.ControllerName, redirect.ActionNameget the name of Action is also not a real controller, but above a method for assigning Thus their values are always equal.

We can make the test more meaningful by the following transformation

       [Fact]
        public async Task ViewTest()
        {
            HomeController hc = new HomeController();
            var result = hc.Index(null);
            var redirect = (RedirectToActionResult) result;
            var controllerName = redirect.ControllerName;
            var actionName = redirect.ActionName;
            Assert.True(
                controllerName.Equals(nameof(HomeController).GetControllerName(),
                    StringComparison.InvariantCultureIgnoreCase) && actionName.Equals(nameof(HomeController.Contact),
                    StringComparison.InvariantCultureIgnoreCase));
        }

The above code we use nameof get the name of the type or method, and then manually write the judge and acquired by nameof is not the same, so if we handwriting errors will be found, but there is a problem is that we get through the nameof HomeController names are strings HomeControllerinstead of Home, other types too, but this is very easy to handle because they are ending Controller, as long as we were about it and deal with it. we look GetControllerName method, which is an extension of the string class method

 public static class ControllerNameExtension
    {
        public static string GetControllerName(this string str)
        {
            if (string.IsNullOrWhiteSpace(str) || !str.EndsWith("Controller",StringComparison.InvariantCultureIgnoreCase))
            {
                throw new InvalidOperationException("无法获取指定类型的ControllerName");
            }

            string controllerName =
                str.Replace("Controller", string.Empty, StringComparison.InvariantCultureIgnoreCase);
            return controllerName;
        }
    }

This method is very simple, is the result of the Controller class 'Controller' remove string

Since ControllerFactory when creating the Controller is not case-sensitive, so we are all equals plus a case-insensitive option, which led approach seems particularly long, we have to make a simple package.

 public static class StringComparisionIgnoreCaseExtension
    {
        public static bool EqualsIgnoreCase(this string str, string other)
        {
            return str.Equals(other, StringComparison.InvariantCultureIgnoreCase);
        }
    }

The above method is very simple, that is plus when comparingStringComparison.InvariantCultureIgnoreCase

Assert assertion becomes final code is as follows:

 Assert.True(
                controllerName.EqualsIgnoreCase(nameof(HomeController).GetControllerName()) && actionName.EqualsIgnoreCase(nameof(HomeController.Contact)));

So, if we are wrong because the handwriting misspelled names or multiple spaces can easily be identified, and if the name of the method to get rid here a compiler error will help us locate the error.

确保程序重定向到正确路由

Sometimes we redirect to a specified route, take a look at how to test

public IActionResult Index(int? id)
        {
            if (!id.HasValue) return RedirectToRoute(new{controller="Home",action="Contact"});
            return View("Index","hello");
        }

If the above method is null id to redirect to a route, here briefly about how to create such an anonymous object, why the object's name as controller, and action rather than controllername and actionname? Mvc we can run the program to see where RouteData What is the name of the key-value pairs that will understand.

Test methods are as follows

       [Fact]
        public async Task ViewTest()
        {
            HomeController hc = new HomeController();
            var result = hc.Index(null);
            var redirect = (RedirectToRouteResult) result;
            var data = redirect.RouteValues;
            var controllerName = data?["controller"]?.ToString();
            var actionName = data?["action"]?.ToString();
            Assert.True(!string.IsNullOrWhiteSpace(controllerName));
            Assert.True(!string.IsNullOrWhiteSpace(actionName));
            Assert.True(controllerName.EqualsIgnoreCase(nameof(HomeController).GetControllerName()));
            Assert.True(actionName.EqualsIgnoreCase(nameof(HomeController.Contact)));
        }

In fact, the above methods and test RedirectToAction nature above are similar guide to determine the correct controller and action, the difference is that the value of the acquisition method.

RedirectToAction and RedirecttoRoute route can pass values ​​above values ​​in order to obtain the sample index key is no longer expand the teachings herein.

Ensure proper redirected to the designated short url

.net core in a new LocalRedirect(and corresponding permanent rewrite to permanently redirect keeping methods, other similar methods to redirect these also have family). It is similar to RedirecttoRoute, just a parameter is not RouteData, and is a short circuit caused by (without the host name and ip, because the default and only internal redirect).

We Index method in HomeController read as follows:

 public IActionResult Index(int? id)
        {
            if (!id.HasValue) return LocalRedirect("/Home/Hello");
            return View("Index","hello");
        }

If Id is null redirect to /home/Hellosurely we wrote a lot of code like this when the page request to the backend, there is no longer explained in detail.

Test methods are as follows:

       [Fact]
        public async Task ViewTest()
        {
            HomeController hc = new HomeController();
            var result = hc.Index(null);
            var redirect = (LocalRedirectResult) result;
            var url = redirect.Url.Split("/").Where(a=>!string.IsNullOrEmpty(a));
        }

This is mainly acquired through Url to this address, and then divide it into several parts. The first part is the default controller name, the second part is the action name. Behind the code is no longer write, you try it yourself.

It should be noted that all of the above examples only deal with the default route, and did not deal with routing parameters, custom routing, and aera routing, etc. If the route is not the default, the first part of the above is not necessarily controller name , and there is also need to address the actual business.

view test

On a knowledge regarded as additional knowledge to the test mvc controller. This festival started to explain about the integration test in view of mvc.

One thing to understand is that integration testing by sending a http request is unable to get into the program of the Controller object, we can only View page integration testing.

The test page contains a major test of the test and return status of the page content. Ensure that the correct response is generated and returned to the correct page, in front of the main test unit testing is to return the name of view is correct, as to whether to reach this page may not. integration testing where we want to determine the identity of the current page according to the characteristics of the current page. this page has is unique, it can distinguish between different pages and other features.

We still use the Index as a case under HomeController to explain. For the Index method changed the factory settings, as follows

 public IActionResult Index()
        {
            return View();
        }

First page here to return to the carousel which contains a map, we can assert that the returned page contains carouselkeywords, test code as follows

        [Fact]
        public async Task ViewIntegrityTest()
        {
            var response = await _client.GetAsync("/Home/Index");
            response.EnsureSuccessStatusCode();
            var responseStr = await response.Content.ReadAsStringAsync();
            Assert.Contains("carousel", responseStr);
        }

The above test returned content (that is, the whole view page) included in carouselthis message.

Note that this page can not be distinguished above the actual project COSCO is the home page, you may need additional judge needs to consider, as appropriate, according to the actual situation, if the content of a specific id, name, etc. may become integrated as a judgment that would make testing bring maintenance problems. sometimes too many pages too big changes lead to large error unit testing, unit testing may be directly given up in tight heavy task, and therefore smaller than the range, determine the content of the finer better, but try to find this page immutable, other things can be distinguished pages. even not distinguish, at least where the page can be determined correctly returned instead of 404 pages. this opens the browser detects whether the ratio of the page on the line manually normally open to more reliable.

Still One thing to note is that not integration testing by the end of the story, we still have a page for sampling in the on-line project, see the page layout is normal. Of course, these can also be automated to complete, but sampling is still necessary, do not All methods are seamless.

Guess you like

Origin www.cnblogs.com/tylerzhou/p/11361598.html