MVC Basic Tools (unit tests for Visual Studio, using MOQ)

Source: Internet
Author: User

Unit Testing for 3.Visual Studio

There are many. NET unit test packages, many of which are open source and free. This article intends to use the built-in unit test support that is included with Visual Studio, but some others. NET unit Test package is also available.

To demonstrate unit test support for Visual Studio, this example intends to add a new implementation of the Idiscounthelper interface to the sample project. Under the Models folder, create a new class file MinimumDiscountHelper.cs:

namespace essentiatools.models{public    class Minimumdiscounthelper:idiscounthelper    {public        decimal ApplyDiscount (decimal totalparam)        {            throw new NotImplementedException ();}}    }

The goal of this example is to have minimumdiscounthelper demonstrate the following behavior:

· Discount is 10% when Total is greater than $ A

· When the total amount is between (and includes) $10~$100, the discount is $

· No discount when total is less than $

· When the total value is negative, throw ArgumentOutOfRangeException

3.1 Creating a Unit test project

Take the "MVC 4" 3.MVC basic tool (Create a sample project, use Ninject) of the project "Essentiatools", right-click the top-level entry in Solution Explorer, choose "Add New Project" from the pop-up menu

In the dialog box that pops up, add "unit test Project", set the project name to Essentiatools.tests

Then add a reference to the test project so that you can perform tests on the classes in the MVC project.

3.2 Creating a unit Test

Add unit tests to the UnitTest1.cs file of the Essential.tests project:

Using system;using Microsoft.VisualStudio.TestTools.UnitTesting; using Essentiatools.models; namespace essentiatools.tests{    [TestClass] public    class UnitTest1    {        private idiscounthelper Gettestobject ()        {            return new Minimumdiscounthelper ();        }        [TestMethod]        public void discount_above_100 ()        {            //prepare            idiscounthelper target = Gettestobject ();            Decimal total = $;            Action            var discountedtotal = target. ApplyDiscount (total);            Assert            assert.areequal (total * 0.9M, discountedtotal);        }}}    

Only one unit test was added. The class containing the test is annotated with the TestClass annotation attribute, where each test is annotated with the TestMethod annotation attribute. Not all methods in the Unit test class are unit tests. For example, Gettestobject method because the method does not have TestMethod annotation attributes, Visual Studio does not treat it as a unit test.

As you can see, the unit test method follows the "Prepare/action/ASSERT (A/A/A)" mode.

The above test method is established by invoking the Gettestobject method, and the Gettestobject method creates an instance to be tested-in this case, the Minimumdiscounthelper class. It also defines the total value to be checked, which is the "Ready (Arrange)" section of the unit test.

For the Action (Act) section of the test, call the Minimumdiscounthelper.applediscount method and assign the result to the discountedtotal variable. Finally, the Assert.AreEqual method is used in the Assert (Assert) section of the test to check that the value obtained from the Applediscount method is 90% of the original total.

The Assert class has a series of static methods that you can use in your tests. This class is located in the Microsoft.VisualStudio.TestTools.UnitTesting namespace, which also contains some other classes that are useful for building and executing tests. For a class of this namespace, see: Https://msdn.microsoft.com/en-us/library/ms182530.aspx

The Assert class is the most used one, and some of the important methods are as follows:

Each static method in an Assert class can examine one aspect of a unit test. If the assertion fails, an exception is thrown, which means that the entire unit test fails. Since each unit test is processed independently, other unit tests will continue to be performed.

Each of these methods has a string that is an overload of the argument, which serves as the message element when the assertion fails. The AreEqual and Arenotequal methods have several overloads to satisfy a particular type of comparison. For example, there is one version that can compare strings without having to consider other situations.

Tip: A notable member of the Microsoft.VisualStudio.TestTools.UnitTesting namespace is the ExpectedException property. This is an assertion that the assertion is successful only if the unit test throws an exception of the type specified by the Exceptiontype parameter. This is a neat way to ensure that unit tests throw exceptions without the need to construct a try in a unit test. Catch block

In order to verify the other behavior of the aforementioned Minimumdiscounthelper, modify the file UnitTest1.cs as follows:

Using system;using microsoft.visualstudio.testtools.unittesting;using Essentiatools.models;namespace             essentiatools.tests{[TestClass] public class UnitTest1 {private Idiscounthelper gettestobject () {        return new Minimumdiscounthelper ();  } [TestMethod] public void discount_above_100 () {//prepare idiscounthelper target =            Gettestobject ();            Decimal total = 200; Action var discountedtotal = target.            ApplyDiscount (total);        Assert Assert.AreEqual (total * 0.9M, discountedtotal); }[TestMethod]public void discount_between_10_and_100 () {//prepare idiscounthelper target = Gettestobject (); Action decimal Tendollardiscount = target.            ApplyDiscount (10); Decimal Hundreddollardiscount = target.            ApplyDiscount (100); Decimal Fiftydollardiscount = target.            ApplyDiscount (50);            Assert Assert.AreEqual (5, Tendollardiscount, "$ discount is wrong");            Assert.AreEqual (Hundreddollardiscount, "$ discoutn is wrong"); Assert.AreEqual (45,Fiftydollardiscount, "$ discount is wrong"); } [TestMethod] public void discount_less_than_10 () {Idiscounthelper target = Gettestobjec            T (); Decimal discount5 = target.            ApplyDiscount (5); Decimal discount0 = target.            ApplyDiscount (0);            Assert.AreEqual (5, DISCOUNT5);        Assert.AreEqual (0, discount0); } [TestMethod] [ExpectedException (typeof (ArgumentOutOfRangeException))] public void discount_negative            _total () {Idiscounthelper target = Gettestobject (); Target. ApplyDiscount (-1); }}}

3.3 Running unit tests (and failed)

Visual Studio 2012 introduces a more useful "Test Explorer" window for managing and running tests. You can see this new window by selecting "Window", "Test Explorer" from the "Test" menu in Visual Studio, and clicking the "Runall (Run All)" button near the top left corner. will see the effect:

You can see the defined Test list in the left panel of the window. All of the tests failed, of course, because the methods that were tested were not implemented. You can click any of these tests, and the cause and details of the test failure are displayed in the right panel of the window.

3.4 Implementation Features

Now, it's time to implement the feature. When the coding is done, you can basically be confident that the code works as expected. With the previous preparation, the implementation of the Minimumdiscounthelper class is fairly straightforward:

Using system;using system.collections.generic;using system.linq;using system.web;namespace EssentiaTools.Models{ Public    class Minimumdiscounthelper:idiscounthelper    {public        decimal applydiscount (decimal Totalparam)        {            if (Totalparam < 0)            {                throw new ArgumentOutOfRangeException ();            }            else if (Totalparam >)            {                return totalparam * 0.9M;            }            else if (Totalparam > && totalparam <=)            {                return totalParam-5;            }            else            {                return  totalparam;    }}}}

3.5 Testing and correcting the code

To demonstrate how to use Visual Studio for unit test iterations, the code above deliberately leaves an error. If you click the Run All button in the Test Explorer window, you can see the effect of the error. The test results are as follows:

As you can see, three unit tests have been passed, but the discount_between_10_and_100 test method detects a problem. When you click on this failed test, you can see that the test expects to get 5, but the actual results are 10.

At this point, a re-examination of the code will reveal that it has not been properly implemented-especially if the total is 10 or 100 discount, not properly addressed. The problem is on this sentence of the Minimumdiscounthelper class:

... else if (Totalparam > && totalparam <= 100) ...

Although the goal is to establish a direct behavior between (including) $10~$100, but actually excludes the situation equal to $ A, modified to:

... else if (totalparam >= && totalparam <= 100) ...

Rerun the test and all the test code has passed:

4. Using Moq

One of the reasons why the previous unit tests are so simple is because the test is a single class that does not depend on other classes. Of course, there are such classes in the actual project, but often you need to test some objects that cannot be run in isolation. In these cases, you will need to focus on the class or method of interest before you do not have to implicitly test the dependent class.

A useful approach is to use a mock object, which simulates the function of the actual object in a project in a special and controlled way. Mimicking objects reduces the focus of testing so that users only check for features that are of interest.

4.1 Understanding the problem

Before you start using MOQ, this example tries to demonstrate a problem that you are trying to fix. The following is intended to unit test the Linqvaluecalculator class, linqvaluecalculator in the previous, the specific code is:

Using system;using system.collections.generic;using system.linq;using system.web;namespace EssentiaTools.Models{ Public    class Linqvaluecalculator:ivaluecalculator    {        private idiscounthelper discounter;        Public Linqvaluecalculator (Idiscounthelper discountparam)        {            discounter = Discountparam;        }        Public decimal valueproducts (ienumerable<product> products)        {            return discounter. ApplyDiscount (Products. Sum (P = p.price));}}}    

In order to test this class, add the unit test file UnitTest2.cs in the Unit test project:

Using system;using microsoft.visualstudio.testtools.unittesting;using essentiatools.models;using System.Linq;                                         namespace essentiatools.tests{[TestClass] public class UnitTest2 {private product[] products = {                                         New Product{name= "Kayak", catogory= "watersports", price=275m}, New Product{name= "Lifejacket", catogory= "watersports", price=48.95m}, NE W product{name= "Soccer Ball", catogory= "soccer", price=19.50m}, new Product{name= "Co        Rner flag ", catogory=" soccer ", price=34.95m}}; [TestMethod] public void sum_products_correctly () {//prepare var discounter = new MINIMUMD            Iscounthelper ();            var target = new Linqvaluecalculator (discounter); var goaltotal = products.            Sum (e = e.price); Action var result = target. ValueprodUcts (products);        Assert assert.areequal (goaltotal, result); }    }}

The problem now is that the Linqvaluecalculator class relies on the implementation of the Idiscounthelper interface to operate. This example uses the Minimumdiscounthelper class (which is the implementation class for the Idiscounthelper interface), which shows two different problems.

The first problem is that unit testing becomes complex and fragile. In order to create a unit test that works, you need to consider the discount logic in the Idiscounthelper implementation to determine the expected value of the Valueproducts method. Fragility comes from the fact that the test will fail once the discount logic in the implementation changes.

The second and most worrying problem is that the scope of this unit test has been extended, so that it implicitly contains the Minimumdiscounthelper class. When a unit test fails, the user does not know whether the problem is in the Linqvaluecalculator class or in the Minimumdiscounthelper class.

When unit testing is simple and focused, it works well, and the current settings make both features impossible to satisfy. Adding and applying MOQ to the MVC project avoids these problems.

4.2 Adding Moq to the VisualStudio project

As with the previous Ninject, search for and add the NuGet package Moq in the test project.

4.3 Adding mock objects to Unit tests

Adds a mock object to a unit test to tell Moq which object the user wants to use. Configure its behavior, and then apply the object for testing purposes.

Use mock objects in unit tests, add mock objects for Linqvaluecalculator unit tests, modify UnitTest2.cs files:

Using system;using microsoft.visualstudio.testtools.unittesting;using essentiatools.models;using System.Linq;using                                         Moq;namespace essentiatools.tests{[TestClass] public class UnitTest2 {private product[] products = {                                         New Product{name= "Kayak", catogory= "watersports", price=275m},                                         New Product{name= "Lifejacket", catogory= "watersports", price=48.95m}, New Product{name= "Soccer Ball", catogory= "soccer", price=19.50m}, new Product{name        = "Corner flag", catogory= "soccer", price=34.95m}}; [TestMethod] public void sum_products_correctly () {//Preparemock<idiscounthelper> Mock = new mock<idiscounthelper> (); Mock. Setup (M = M.applydiscount (it.isany<decimal> ())). Returns<decimal> (total = total); var target = new Linqvaluecalculator (mock. Object); Action var result = target.            Valueproducts (products); Assert Assert.AreEqual (products.        Sum (e = e.price), result); }    }}

When you use MOQ for the first time, you may find the syntax a bit strange, and the following shows each step of the process.

(1) Creating mock objects

The first step is to tell Moq what kind of mock object the user wants to use. MOQ relies heavily on generic type parameters, and you can see how this parameter is used from the following statement, which is the idiscounthelper implementation that tells Moq to emulate an object.

... mock<idiscounthelper> mock = new mock<idiscounthelper> ();

Create a strongly typed mock<idiscounthelper> object to tell the MOQ library what type it is dealing with--and, of course, this is the Idiscounthelper interface for that unit test. To improve the focus of unit testing alone, this can be any type that you want to isolate.

(2) Selection method

In addition to creating a strongly typed mock object, you also need to specify how it behaves-the core of the simulation process, which establishes the baseline behavior required for impersonation, which users can use to test the functionality of the target object in the unit test. The following is the statement in the unit test that establishes the behavior that the user wants for the mock object.

... mock. Setup (M = M.applydiscount (it.isany<decimal> ())). Returns<decimal> (total = total), .....

Add a method to the mock object by using the Setup method. Moq works with LINQ and lambda expressions. When the Setup method is called, Moq passes the interface that requires it. It cleverly encapsulates some of the LINQ magic that the book does not intend to elaborate, which allows the user to choose a method to configure or check with a lambda expression. For this unit test, you want to define the behavior of the Applediscount method, which is the only method of the Idiscounthelper interface and the method that is required to test the Linqvaluecalculator class.

You must tell Moq what the user is interested in, which is what the IT class is going to do, as shown in the bold section below.

... mock. Setup (M = M.applydiscount (it.isany<decimal> ())). Returns<decimal> (total = total), .....

This it class defines a number of methods that are used with generic type parameters. This example calls the IsAny method with decimal as a generic type. This tells Moq that when calling the ApplyDiscount method with any decimal parameter, it should use the behavior we define.
The methods provided by the It class are given below, and all of these methods are static.

(3) Define the results

The Returns method lets the user specify the result to be returned when the impersonation method is called. The type parameter is used to specify the type of the result, and a lambda expression is used to specify the result. As follows:

...
Mock. Setup (M = M.applydiscount (it.isany<decimal> ())). returns<decimal> (total = total);
...

By calling the Returns method with the decimal type argument (that is, returns<decimal>), this tells Moq to return a decimal value. For a lambda expression, Moq passes a type value that is received in the ApplyDiscount method-this example creates a Pierce method that returns the value of the ApplyDiscount method passed to the imitation, and does not perform any action on the value.

The idea of the above process is:

In order to unit test Linqvaluecalculator, if you create a idiscounthelper mock object, you can exclude the Idiscounthelper interface implementation Class Minimumdiscounthelper in the unit test, This makes unit testing simpler and easier. The entire process of creating a mock object with MOQ consists of the following steps: A. Use a mock to create a mimic object; b. Use the Setup method to establish the behavior of the Imitation object; c. Use the It class to set the parameters for the behavior; d. Use the return method to specify the return type of the behavior, E. Use a lambda expression to establish a specific behavior in the return method.

(4) Using mock objects

The final step is to use this mock object in the unit test, by reading the value of the object property of the Mock<idiscounthelper> objects to achieve

... var target = new Linqvaluecalculator (mock. Object), .....

In summary, in the above example, the Object property returns the implementation of the Idiscounthelper interface, in which the ApplyDiscount method returns the value of the decimal parameter that it passes.

This makes unit tests easy to perform because the user can take the sum of the price of the Product object and check that the Linqvaluecalculator object gets the same value.

...
Assert.AreEqual (Products. Sum (e = e.price), result);

The advantage of using MOQ in this way is that unit tests only check the behavior of Linqvaluecalculator objects and do not rely on the true implementation of Idiscounthelper interfaces in any Models folder. This means that when a test fails, the user knows that the problem is in the Linqvaluecalculator implementation, or the way in which the object is modeled. Solving problems from these aspects is simpler and easier than dealing with actual object chains and interacting with each other.

4.4 Creating more complex mock-up objects

A very simple mock-up is shown earlier, but the most beautiful part of MOQ is the ability to quickly build complex behaviors to test different situations. Create a new unit test in UnitTest2.cs to emulate a more complex idiscounthelper interface implementation.

Using system;using microsoft.visualstudio.testtools.unittesting;using essentiatools.models;using System.Linq;using                                         Moq;namespace essentiatools.tests{[TestClass] public class UnitTest2 {private product[] products = {                                         New Product{name= "Kayak", catogory= "watersports", price=275m},                                         New Product{name= "Lifejacket", catogory= "watersports", price=48.95m}, New Product{name= "Soccer Ball", catogory= "soccer", price=19.50m}, new Product{name        = "Corner flag", catogory= "soccer", price=34.95m}}; [TestMethod] public void sum_products_correctly () {//Prepare mock<idiscounthelper> M            Ock = new mock<idiscounthelper> (); Mock. Setup (M = M.applydiscount (it.isany<decimal> ())).            Returns<decimal> (total = total); var target =New Linqvaluecalculator (mock.            Object); Action var result = target.            Valueproducts (products); Assert Assert.AreEqual (products.        Sum (e = e.price), result);         }Private product[] Createproduct (decimal value) {return new[] {new Product {price = value}}; } [TestMethod] [ExpectedException (typeof (System.ArgumentOutOfRangeException))] public void Pass_thro            Ugh_variable_discounts () {mock<idiscounthelper> Mock = new mock<idiscounthelper> (); Mock. Setup (M = M.applydiscount (it.isany<decimal> ())).            Returns<decimal> (total = total); Mock. Setup (M = M.applydiscount (it.is<decimal> (v = = = 0)).            Throws<system.argumentoutofrangeexception> (); Mock. Setup (M = M.applydiscount (it.is<decimal> (v = v > 100)).            returns<decimal> (total = 0.9M); Mock. Setup (M = M.applydiscount (It.isinrange<decimal> (Ten, Range.inclusive))).            returns<decimal> (total = total-5); var target = new Linqvaluecalculator (mock.            Object); Decimal Fivedollardiscount = target.            Valueproducts (Createproduct (5)); Decimal Tendollardiscount = target.            Valueproducts (Createproduct (10)); Decimal Fiftydollardiscount = target.            Valueproducts (Createproduct (50)); Decimal Hundreddollardiscount = target.            Valueproducts (createproduct (100)); Decimal Fivehundreddollardiscount = target.            Valueproducts (Createproduct (500));            Assert.AreEqual (5, Fivedollardiscount, "$ Fail");            Assert.AreEqual (5, Tendollardiscount, "$ Fail");            Assert.AreEqual (Fiftydollardiscount, "$ Fail");            Assert.AreEqual (Hundreddollardiscount, "$ Fail");            Assert.AreEqual (Fivehundreddollardiscount, "$ Fail"); Target. Valueproducts (createproduct (0)); }}}

Copying the expected behavior of another model class during unit testing seems to be a strange thing to do, but it perfectly demonstrates some of the different uses of MOQ.

As you can see, four different behaviors of the ApplyDiscount method are defined based on the values of the parameters received. The simplest behavior is "full match", which returns any decimal value directly, as follows:

Mock. Setup (M = M.applydiscount (it.isany<decimal> ())). Returns<decimal> (total = total);

This is the same behavior for the previous example, which is put in this because the order in which the Setup method is called affects the behavior of the mock object. MOQ evaluates the given behavior in reverse order, and therefore considers calling the last Setup method. This means that the user must carefully create the impersonation behavior from the most general to the most special order. It.isany<decimal> is the most general condition defined in this example, so it is used first. If you reverse the order in which Setup is called, the behavior will match all calls to the ApplyDiscount method and generate an incorrect imitation result.

(1) Imitate a specific value (and throw an exception)

For the second invocation of the Setup method, the It.is method is used

Mock. Setup (M = M.applydiscount (it.is<decimal> (v = = = 0)). throws<system.argumentoutofrangeexception> ();

If the value passed to the ApplyDiscount method is 0, then the predicate of the IS method returns True. Instead of returning a result, the Throws method is used, which causes Moq to throw an exception instance specified with the type parameter.

The example also captures a value greater than 100 with the IS method:

Mock. Setup (M = M.applydiscount (it.is<decimal> (v = v >)). returns<decimal> (total = 0.9M);

The Is.it method is the most flexible way to establish a specified behavior for different parameter values, because the user can use any predicate to return TRUE or false. This is the most common method of creating complex mock objects.

(2) Range of imitation values

The It object is finally used in conjunction with the Isinrange method, which allows the user to capture the range of parameter values.

Mock. Setup (M = M.applydiscount (It.isinrange<decimal> (Ten, Range.inclusive))). returns<decimal> (total = total-5);

This approach is presented here for completeness, and if it is in the user's own project, you can use it methods and a predicate to do the same thing as follows:

Mock. Setup (M = M.applydiscount (it.is<decimal> (v=>v>=10&&v<=100))). returns<decimal> (total = total-5);

The effect is the same, but the predicate method is more flexible. Moq has a number of very useful features, and you can see a lot of usage by reading the Getting Started Guide available on the Https://github.com/Moq/moq4/wiki/Quickstart.

Source Address: Https://github.com/YeXiaoChao/EssentiaTools

Reference http://www.cnblogs.com/yc-755909659/p/5254427.html

MVC Basic Tools (unit tests for Visual Studio, using MOQ)

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.