Unit Test .Net Core MVC with MSTestV2, MOQ and Fluent Assertions

Introduction

This article explains the way to Unit Test .Net Core MVC with MSTestV2, FluentAssertions, and MOQ framework. It is applicable to both .Net Core and .Net applications and Unit testing fundamentals remain very much the same across all platforms including non-Microsoft platforms like Java/Python etc. This article will a sample Asp.Net Core MVC web application as a system under test and use MSTestV2 framework for unit testing along with Fluent Assertions and Fluent Assertions for Asp.Net Core MVC and MOQ framework.

The article is focused on showing key aspects of MOQ, FluentAssertions and Unit Testing fundamentals. If you want details on MSTestV2 testing framework specifically, I will be adding another article on that or you can look in other online resources for that. one of them is a very good Pluralsight course which you can follow:

https://www.pluralsight.com/courses/mstest-v2-automated-testing

What is Web Application Testing? Why not just implement UI Test Automation for our web applications?

Before we dive into unit testing and its benefits, it’s important to understand the value of software testing in general and different ways we can handle software testing.

As software developers, we implement different kinds of software components and products using various technology stacks like .Net/Java/PHP/Python/JavaScript etc which is great. It is also equally important to ensure that these components work fine in all possible scenarios and the quality of our product is optimal and that it should not fail in any given scenario. In the case of web and mobile applications source of input to the applications is some sort of user input where user interacts with these applications using a client like a web browser.

There are different phases in the software development life cycle (SDLC) where we can manage the quality of our software. In the case of Web Applications, one possible approach for software quality is to implement a UI (user interface) Test Automation Framework using tools like Selenium or Coded UI. This test suite can then be run before every product release to ensure that all UI screens and scenarios work as expected and nothing breaks in the final product. This could work but UI Test automation is part of the Testing phase and it is implemented only after implementation of the actual product. With this approach, it would also mean that developers start releasing code to the Test team without testing themselves and this can potentially lead to a lot of time and productivity issues where all responsibility of software quality lies with the Test team and this, in turn, could lead to a recursive cycle where the testing team push the release back to development as they find bugs. This is something not at all acceptable in today’s DevOps world where the expectation is to deliver high-quality code in first shot itself and no organization can sustain a model where development and test teams works in isolation.

Secondly, UI Test Automation is to test and check that all screens and functionalities works as per the business and functional requirements and it is not to test the granular behavior of underlying code base.

  • As an example, we can’t rely on UI Test suite to check if correct .Net core exception is thrown by underlying code in a given failure scenario or not.
  • Another example where UI Test suite may not help is when we want to test all possible outcomes from a given code component given a set of inputs. In other words, we want to ensure that each and every code component works as per the technical design and that we have handled all possible success and failure scenarios. This is something that may not be handled with UI Test Suites and if handled it will turn out to be too costly to implement and maintain.

UI Tests are part of integration testing where multiple layers of software products like frontend – middleware and backend work together to produce the result. Since they span all layers of software they tend to run slow as well. If our system is big and we run every possible testing scenario using UI Tests then it would need a huge timeslot to run all the tests which is not very productivity and doesn’t provide a great value.

Considering all above points, we can clearly see where unit testing can provide great value specially in today’s agile and DevOps world.

What is Unit Testing Automation and what are its Benefits

Handle errors during the development phase itself – Unit Testing is about testing individual units of code in isolation during the development phase itself rather than pushing it for the testing team. A software application consists of different layers where all these layers work with each other to produce the desired output. The idea behind unit testing is to isolate each of these layers and test them in isolation to ensure that each unit of code in these layers (like functions or methods in .Net) works correctly for all possible input scenarios including any error or exception handling.

Shift testing left in SDLC for improved and productivity benefits – Core idea behind unit testing automation is that if all individual units of code and each layer in software behaves correctly in isolation then it would also work correctly when these layers work in conjunction with each other. This leads to greater overall productivity and shortens the testing phase. Also, the vicious and recursive cycle of code push back to development teams is mitigated. Unit Tested code also ensures that the development team is more confident before pushing the release to next phase.

Automation of Unit Test Automation is of prime importance – For very small projects unit testing may be done manually but as our system grows, doing manual unit testing beyond a certain point won’t be practical. As the system grows effort to do manual unit testing also increases and it will be error-prone too due to human involvement. Secondly, manual unit testing even if possible is a very low-value work and leads to productivity losses and demotivated and bored teams due to its repetitive and redundant nature. Unit test automation is the solution to these problems where we automate our unit tests through code which can then run periodically and keep a close check on software code quality at a very granular level.

Resiliency from code errors – Unit Test automation also adds great value as our software product is enhanced with time and provides great resiliency from errors. As code changes are made with time, developers may only be focusing on areas where they have made changes and implement unit tests only for that section and there is always a possibility that change in one area may lead to breaking any existing functionality. If we have a detailed unit test suite in place, then we can them after every code change to ensure that it doesn’t break other areas. If any unit tests are failing then it would mean either our code has broken the existing functionality or we may need to update the unit tests itself to adjust to new functionality.

A great product documentation – Unit Tests suite also acts as great software documentation in itself if we follow good design principals and patterns to implement them. In DevOps and agile world, no one has time to keep documentation updated as software systems evolve. Also updating documentation with an evolving software system is a very difficult task and based on my industry experience, having documentation outside of code don’t work well and adds to product maintenance cost. By implementing well-written unit tests, existing and new developers in the team can take a look at them to understand code behavior.

How is Unit Testing Automation Performed

To implement unit tests, we need a test framework that supports a given technology stack. In case of .Net and .Net Core, there are many frameworks available in the market like Microsoft Testing Framework called MSTest as well as other third party software packages like NUnit and XUnit. Secondly, since Unit Testing deals with testing individual units of code in isolation, its generally performed in conjunction with software packages that are capable of isolating a piece of code in our software product. There are many frameworks available for this like MOQ, RhinoMoq, Microsoft Fakes etc and in this article I have used MOQ.

MSTestV2 is a cross-platform and open-source Test Framework from Microsoft and works with .Net as well as .Net Core. It’s basically the next version of the original MStest which only supported .Net Framework earlier. MOQ framework helps in isolating a piece of code (which generally means a Class in .Net Core) to that code can tested independently without the need of other integration layers and this also makes running unit tests very fast. Most unit testing frameworks provide in-built assertion libraries to test pass/fail of tests but there are libraries available that provides much better assertion libraries and make unit tests more natural to understand.I have used FluentAssertions and FluentAssertions.AspNetCore.MVC third party nuget packages for this purpose. If you don’t know what is assertion, then please follow along.

Brief overview on Unit Test Implementation and Mocking

Unit Tests are to test actual code component which is generally called SUT or system under test. To implement a unit test we follow a standard design pattern which is called AAA or Arrange- Act-Assert.

a) Arrange – in this part of the test, we arrange all the variables and dependencies required to call the actual code or SUT. This is the place where we isolate our code component from other dependencies (like the service layer or maybe the data access layer) by the help of mock frameworks. Mocking means replacing the actual implementation of dependencies with a mock implementation where a call to that integration layer will be replaced by a mock call and will return a mock response back to our code. This will be demonstrated in next sections.

b) Act – in previous phase, we have arranged all data requirements and mocked whatever required. In this part, we call the actual system under test or unit of code.

c) Assert – in the above phase, the actual system was called and run and will so some processing. In this phase, we test if the code is behaving as expected or not. For this, we use various assert statements that are generally part of the core testing framework being used and compare the actual output returned from the main code with the output we expect. If expected becomes equal to actual then the unit test is considered a pass else a fail. For asserting we have in-built assertion functionalities in testing frameworks or we can use third party packages and frameworks. Here we will use FluentAssertions as mentioned previously.

Solution Summary

**I will be sharing the same project sample in GitHub repository. Details are in next section**

  • Asp.Net Core MVC web application – its a sample application demonstrating a very basic eCommerce system with Product Listing, Add to Cart, and Checkout screens. There is nothing great about this app and is using pretty standard .Net Core concepts.
    • Service Layer – Products are fetched from an In-memory product service. I am using a hardcoded set of products list. Service implements the IProductService interface having some standard CRUD operations. InMemoryProductService.cs class implements this interface. The service layer is embedded in same web project for demonstration purposes.
    • Startup.cs – Service is registered in startup.cs file as a singleton service – I am doing this to add state persistence to the products list ie to keep track of what products are added to cart and how many are remaining.
    • View – Product Listing View displays all the products and their availability status.
    • View -AddToCart View will show products added to the cart when the user clicks the AddToCard button.
    • View -Checkout view will show all products added to the cart and also act the final screen for this demonstration.
    • Styling – Some standard bootstrap4 to style these views.
    • ViewModel ProductViewModel.cs to keep products for View.
    • ViewModel BasketViewModel.cs to keep a list of products in the basket for Checkout view.
    • Controller ProductController.cs has all the action methods including Listing, AddToCart, and Checkout rendering their respective views.
  • Unit Testing Project – MVCCoreApplication.UnitTests – main web application has been added as a project reference in the Unit Test Project. It uses MSTestV2 testing framework which is the latest open source and cross-platform testing framework from Microsoft. It works with both .Net framework as well as .Net Core framework
    • NuGet Package – FluentAssertions NugGet package has been added and it provides a better approach to handle assertions in the test projects. This package provides assertions that make unit test code easier to understand and provides a very natural way to read and understand assert statements. Code snippets in below sections will explain it better.
    • NuGet Package – FluentAssertions.AspNetCore.Mvc NuGet package has been added as well. It provides great natural assertions specifically for the .net core MVC framework.
    • NuGet PackageMoq nuget package has been added to isolate the code components/classes.

GitHub Repository Link

https://github.com/ksmslearnings/MvcCoreUnitTesting – entire project is available in this GitHub repository for your reference.

Below screen shot shows the project structure.

Sample Solution For Unit testing

Nuget Packages added in Test Project shown below – Please note packages starting with MSTest.* are part of MSTestV2 test Framework itself and are not manually added.

Unit Test Project with MSTestV2 and Fluent Assertions

Details – MVC Web Application – ProductController.cs

Product Controller has 3 main methods – It has nothing special except that for adding complexity I have added Session, Request and QueryString objects.

  • Listing method lists all the products available. It gets these products from In-memory product service. To add to complexity in code, I have used some logic as below
    1. If Product Description is not passed in the query string in Product/Listing – it lists all the products.
    2. If Product Description is passed correctly in the query string to Product/Listing action method like ?p=xyz – we retrieve xyz and read it as ProductDescription and use it to retrieve filtered products. Also, we store the filtered products in the Session object.
    3. If the Product Description is passed as a numeric, then we throw an error from the action method.
  • AddToCart method – adds the product to the user cart. Cart is also maintained as a static dictionary in the same Product service.
  • GetBasket method – gets back the products from the user cart or basket.
  • Checkout method – we show the user basket in Checkout view and show thankyou message.

ProductController.cs code snippet below for quick reference

using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using UnitTestingAspNetCoreMVCApplication.Models;
using UnitTestingAspNetCoreMVCApplication.ServiceLayer;
using Newtonsoft.Json;

namespace UnitTestingAspNetCoreMVCApplication.Controllers
{
    public class ProductController : Controller
    {
        private readonly IProductService productService;

        /// <summary>
        /// Constructor - ProductService Injected using .Net Core in-built Dependency Injection Model.
        /// This interface and its concrete implementation is registered in startup.cs file.
        /// </summary>
        /// <param name="productService"></param>
        public ProductController(IProductService productService)
        {
            this.productService = productService;
        }

        public IActionResult Listing()
        {
            var queryString = this.HttpContext.Request.QueryString.Value;
            if (queryString == string.Empty || queryString == null)
            {
                var viewModel = this.productService.ListProducts();
                return View("Listing", viewModel);
            }
            else
            {
                //if querystring passed- get product description and then use it for gettign filtered products.
                //if querystring is numeric then throw error.
                int checkValue = -1;
                queryString = queryString.Substring(1, queryString.Length - 1);
                string productDescription = queryString.Split('=')[1].Trim();

                if (int.TryParse(productDescription, out checkValue))
                {
                    throw new InvalidOperationException("QueryString passed does not " +
                        "have valid product description");
                }
                //check if filtered products available in session. If yes get from there.
                if (this.HttpContext.Session.GetObjectBack<List<ProductViewModel>>("ViewModel") != null)
                {
                    return View(this.HttpContext.Session
                        .GetObjectBack<List<ProductViewModel>>("ViewModel"));
                }
                else
                {
                    //get filtered product list and store in session.
                    var viewModel = this.productService.ListProducts()
                        .Where(x => x.ProductDescription.ToLower().Contains(productDescription.ToLower())).ToList();
                    this.HttpContext.Session
                        .SetObjectToString<List<ProductViewModel>>("ViewModel", viewModel);
                    return View("Listing", viewModel);
                }
            }
        }

        /// <summary>
        /// Get the Cart of the customer. we have a dummy customer for demonstration.
        /// </summary>
        /// <returns></returns>
        private BasketViewModel GetBasket()
        {
            var b = new BasketViewModel();
            b.Basket = this.productService.GetBasket("dummyuser");
            return b;
        }

        /// <summary>
        /// show basket in checkout view.
        /// </summary>
        /// <returns></returns>
        public IActionResult Checkout()
        {
            var b = GetBasket();

            return View(b);
        }


        /// <summary>
        /// check if product available for input product ID
        /// add product to cart which is static dictionary object storing list of products
        /// reduce product availability count by 1.
        /// </summary>
        /// <param name="ProductID"></param>
        /// <returns></returns>
        public IActionResult AddToCart(int ProductID)
        {
            try
            {
                int returnValue = -1;
                ProductViewModel product = null;

                product = this.productService.GetProduct(ProductID);
                if (product != null)
                    returnValue = this.productService.AddToCartForUser(ProductID);
                if (returnValue > 0)
                    returnValue = this.productService.ReduceProductCount(ProductID);

                return View(product);
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }
    }

    /// <summary>
    /// session helper extension methods to set and get the session objects.
    /// .Net core Session doesnt provide direct method to store any object and 
    /// first we need to serealize the object as string and then only can store it 
    /// in session.
    /// </summary>
    public static class SessionExtensions
    {
        public static void SetObjectToString<T>(this ISession session, string key, T value)
        {
            session.SetString(key, JsonConvert.SerializeObject(value));
        }

        public static T GetObjectBack<T>(this ISession session, string key)
        {
            var value = session.GetString(key);
            return value == null ? default : JsonConvert.DeserializeObject<T>(value);
        }
    }
}

Another important file in main web application project is InMemoryProductService.cs – this handle User Basket, Products List and some more logic methods which are kind of self explanatory as below.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using UnitTestingAspNetCoreMVCApplication.Models;
using UnitTestingAspNetCoreMVCApplication.ServiceLayer.DTOEntities;

namespace UnitTestingAspNetCoreMVCApplication.ServiceLayer
{
    public class InMemoryProductService : IProductService
    {
        // public static readonly List<ProductDTO> products = null;
        //private static InMemoryProductService()
        //{
        static List<ProductDTO> products = null;
        static Dictionary<string, List<ProductDTO>> cart = new Dictionary<string, List<ProductDTO>>();

        //    }

        static InMemoryProductService()
        {

            products = new List<ProductDTO>
            {
                new ProductDTO { ProductID=1, ProductCategory=1, ProductDescription="Nokia Mobile", ProductName="Lumia", Available=5},
                new ProductDTO {ProductID=2, ProductCategory=2, ProductDescription="Samsung TV", ProductName="X10", Available=5 },
                new ProductDTO { ProductID=3, ProductCategory=1, ProductDescription="Apple iPhone", ProductName="iPhone X", Available=5},
                new ProductDTO { ProductID=4, ProductCategory=3, ProductDescription="XBox Gaming", ProductName="XBox 360", Available=5}
            };

        }
        
        List<ProductViewModel> IProductService.ListProducts()
        {
            try
            {
                var viewModel = products.Select(x => new ProductViewModel
                {
                    ProductID = x.ProductID,
                    ProductName = x.ProductName,
                    ProductCategory = x.ProductCategory,
                    ProductDescription = x.ProductDescription,
                    Available = x.Available
                }
                 );

                return viewModel.ToList();
            }
            catch (Exception ex)
            {
                throw ex;
            }

            return null;

        }

        int IProductService.AddToCartForUser(int productId)
        {
            List<ProductDTO> addedProducts = new List<ProductDTO>();
            var p = products.Where(x => x.ProductID == productId && x.Available >= 1).FirstOrDefault();

            if (p != null)
            {
                if (!cart.ContainsKey("dummyuser"))
                {
                    addedProducts.Add(p);
                    cart.Add("dummyuser", addedProducts);
                }
                else
                {
                    cart["dummyuser"].Add(p);
                }
                return 1;
            }
            else
            {
                return 0;
            }
        }

        ProductViewModel IProductService.GetProduct(int productId)
        {
            var p = products.Where(x => x.ProductID == productId).FirstOrDefault();
            if (p != null)
                return new ProductViewModel
                {
                    ProductID = p.ProductID,
                    ProductName = p.ProductName,
                    ProductDescription = p.ProductDescription,
                    ProductCategory = p.ProductCategory,
                    Available = p.Available
                };
            else
                return null;

        }

        int IProductService.ReduceProductCount(int productId)
        {
            var p = products.Where(x => x.ProductID == productId && x.Available >= 1).FirstOrDefault();
            if (p != null)
            {
                p.Available = p.Available - 1;
                return 1;
            }
            else
            {
                return 0;
            }
        }


        List<ProductViewModel> IProductService.GetBasket(string user)
        {
            List<ProductViewModel> vm = new List<ProductViewModel>();

            var items = cart[user];
            vm = items.Select(x => new ProductViewModel
            {
                ProductID = x.ProductID,
                ProductName = x.ProductName,
                ProductDescription = x.ProductDescription,
                ProductCategory = x.ProductCategory,
                

            }).ToList();

            return vm;
        }
    }
}

Now lets back to Unit Testing Project

Concept of Mocking

There are many mocking frameworks available and their core idea is to isolate a piece of testable code from all its dependencies. As an example isolating controller layer from any service calls being made. In order to do it, we create a stub or mock service and inject it into the actual testable code or system under test. Here this service will be injected into the ProductController using constructor injection. When the unit test runs, actual service calls will be replaced by the mocked service call since we have already injected it before calling the main testable code.

For better understanding please see below code snippet and specifically check comments with each line of code.

//we have a dummy product list
List<ProductViewModel> productViewModel = new List<ProductViewModel>
{
    new ProductViewModel { ProductID=1, ProductCategory=1, ProductDescription="Nokia Mobile", ProductName="Lumia", Available=5 }
};

//we create a mock of IProductService
Mock<IProductService> productServiceMoq = null;
productServiceMoq = new Mock<IProductService>();


//We set this mock service to return a fixed product list back when its ListProducts method is called.
productServiceMoq.Setup(x => x.ListProducts()).Returns(productViewModel);

//Finally we call ProductController which is our system under test and inject this mock service using constructor injection.
//Please note that for this to work , ProductController need to have a way to inject the mock objects which can be Constructor or a Property Injection.
ProductController sut = new ProductController(productServiceMoq.Object);
 

Above we are using MOQ framework and first step is to create a mock instance of IProductService which is then injected into main testable code ie ProductController.

Every mocking framework may have its own constraints and constraint with MOQ is that we can only create mock objects for types that are either abstract or virtual or a type implementing a set of interfaces. This is because mocking is to override default behavior with a mock behavior and overriding behavior is only possible with abstract or virtual or types implementing an interface and this is based on SOLID principals.

Unit Testing also exposes the existing design issues in our main software architecture where its code is not implemented with the correct design and SOLID principals it becomes very hard to unit test the application since as developers we won’t be able to isolate individual components.

Mocking MVC ControllerContext – HttpContext – Session – HttpRequest Objects

To unit test MVC controllers and web applications in general , we need to handle and manage mocking key web application concepts including HttpSession, HttpContext, HttpRequest and Response, Cookies , Cache etc. Below is one possible approach which can be used and I have been using successfully in my projects.

In order to mock all these web concepts in .Net Core MVC , we need to start with a stub ControllerContext.

ControllerContext – Every .Net Core MVC Controller has a controller context and it is defined as a public class in MVC core framework. Since its a public type , we don’t necessarily need to mock it and can create our own dummy implementation directly and inject it into the actual code or ProductController.

HttpContext – this lies under the ControllerContext and is an abstract class. It can be mocked and its functionality can be overridden and injected in the ControllerContext.

HttpRequest – HttpContext has Request property which is of type HttpRequest. HttpRequest is also an abstract class and same mocking principals applies to this as well.

HttpSession – HttpContext also has HttpSession or Session as its available property and it implements ISession Interface. Since its an interface we can again mock its implementation and inject it into HttpContext.

Fundamentally, if a type is deriving from an interface or is an abstract or a virtual type, then we can always mock that dependency and isolate our unit of testable code from those dependencies.

Below code snippet explains the entire process of mocking these fundamental MVC concepts. First we define a set of mock types and then under TestInitialize method we mock these types one by one. Please refer to comments with each line to understand the implementation better.

We mock all dependent types and finally associate them with stub ControllerContext. This ControllerContext will be associated with ProductController in the actual TestMethod.

        #region variables
        static List<ProductViewModel> productViewModel = null;

        Mock<IProductService> productServiceMoq = null;
        ControllerContext controllerContextMock = null;
        Mock<HttpContext> httpContextMock = null;
        Mock<HttpRequest> httpRequestMock = null;
        Mock<ISession> sessionMock = null;

        #endregion variables

        [TestInitialize]
        public void ProductControllerMockObjectsSetup()
        {
            productServiceMoq = new Mock<IProductService>();
            //Mock the ListProducts() service call
            productServiceMoq.Setup(x => x.ListProducts()).Returns(productViewModel);

            //Create a stub ControllerContext class.
            controllerContextMock = new ControllerContext();
            //Create Mock HttpContext
            httpContextMock = new Mock<HttpContext>();//its an abstract class in .net core.
            //Create Mock HttpRequest
            httpRequestMock = new Mock<HttpRequest>();//its an abstract class in .net core.
            //Create Mock ISession type
            sessionMock = new Mock<ISession>();

            //Setup GetObjectBack and a Session Key to return a dummy ProductViewModel (this is defined as static in Test class)
            sessionMock.Setup(x => x.Set("testKey", new byte[] { 123 }));
            
            //Setup Request and Session properties of mocked HttpContext to return mocked Request and Session objects back.
            httpContextMock.Setup(x => x.Request).Returns(httpRequestMock.Object);
            httpContextMock.Setup(x => x.Session).Returns(sessionMock.Object);

            //Finally set HttpContext property in the stub ControllerContext to this mocked HttpContext
            controllerContextMock.HttpContext = httpContextMock.Object;


        }

Details – Unit Test Method (Arrange – Act – Assert)

Finally we create our unit test which will use all the mock dependencies detailed above.

  • We create a test method and decorate it with TestMethod attribute. This tells MSTest framework that it is a unit test.
  • In the arrange section, we arrange all the necessary dependencies and mock objects ie all the dependencies to call the actual code or a method under test. In this case, ProductController’s controller context is set to the mocked ControllerContext. This injects HttpSession, HttpRequest, HttpContext, and QueryString types inside the ProductController (System Under Test) since they were all associated to ControllerCOntext in the previous section. Without doing this if we call the controller, it would have thrown error since these are external dependencies in the main Product Controller.
  • In Act phase, We call the Controller/Action ie Product Controller –Listing and unit test it.
  • In the Assert phase, finally, we test and assert that the behavior of this call is as per our expectations or not. This is called Assert phase. Here we are using FluentAssertions for Asp.Net Core MVC to handle it. If you see the last two lines you can clearly see that using this package makes a big difference in terms of understanding what’s happening in the check. It follows a very English style natural language based assertion making code much more readable.
       [TestMethod]
        public void ListAllProductsWhenNoQueryStringPassed()
        {
            //Arrange
            //sut == System Under Test
            ProductController sut = new ProductController(productServiceMoq.Object);
            sut.ControllerContext = controllerContextMock;
            sut.ControllerContext.HttpContext = httpContextMock.Object;

            httpRequestMock.Setup(x => x.QueryString).Returns(new QueryString());

            //Act
            var returnValue = sut.Listing();


            //Fluent Assertions usage
            //If return value is a View and View Name as Listing and should return a model of type List<ProductViewModel>
            returnValue.Should().BeViewResult("We return Product Listing view back with Products List", null).WithViewName("Listing");
            returnValue.Should().BeViewResult().ModelAs<List<ProductViewModel>>();


        }

FluentAssertions and FluentAssertions for Asp.Net Core MVC

This sample project is uploaded in my GitHub repo and to understand more on FluentAssertions you can either go through their documentation whcih is pretty nice and crisp.

Below is one more sample where I have handled exceptions test and assertions using FluentAssertions. Here I am checking if a correct exception will be thrown or not and if yes then what is the expected string message in that exception. To check for exceptions we need to use Action or Func delegate approach to call actual code and then check if it throws an exception or a particular type or not.

        [TestMethod]
        public void ThrowErrorWhenQueryStringIsNotValidProductDescription()
        {
            //Arrange            
            httpRequestMock.Setup(x => x.QueryString).Returns(new QueryString("?p=123"));
            //sut == System Under Test
            ProductController sut = new ProductController(productServiceMoq.Object);
            sut.ControllerContext = controllerContextMock;
            sut.ControllerContext.HttpContext = httpContextMock.Object;

            //Act
            Func<IActionResult> act = () => sut.Listing();
            //var returnValue = sut.Listing();

            //Fluent Assertions call should throw an InvalidOperationException with a specific string. Also it should not be assignable to ActionResult type.
            act.Should().Throw<InvalidOperationException>("Query string does not contain valid product description")
                .WithMessage("QueryString passed does not have valid product description")
                .And.Should().NotBeAssignableTo(typeof(IActionResult));


        }

Run the Unit Tests

To run the tests in Visual Studio 2019 we are using the MSTestV2 framework which is Microsoft’s open-source test framework and works on most platforms. It has its own Test Runner and the framework is able to identify what all test methods we have in the project using Test decorations. Visual Studio provides a Test Explorer where we can see all the available Tests and can control their run. Tests can be ran individually or together in a single batch.

Running tests is also possible using Command line in .Net Core and this approach can be used to automate the test run.

Unit Tests Results in Test Explorer

Conclusion

I hope the article would provide you with enough starting knowledge to go ahead and create your own tests. If you feel you would like to see more details in the article please provide your comments and do let me know and I will try to add that as well.

Leave a Comment

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.