Tag: MVC

Database to Unit-Test-Enabled WebApi MVC app in 15 mins

Database to Unit-Test-Enabled WebApi MVC app in 15 mins

Perhaps you’ve just read my previous post Database to full asp.net Mvc app in 5 mins  (or perhaps not) but in any case you find yourself thinking, “..thats all very well but what about testability? I want to know that my approach is testable, and that it supports unit tests, and mocking, and repositories and the like, otherwise the QA team is going to be ALL over me..”

Well, lets see how you can go from an existing database to a unit-test-enabled, asp.net webapi/mvc based app with mockable repositories etc, in just 15 minutes.

[You will need Visual Studio (2012+) and the CodeTrigger For Visual Studio extension (Full Trial, or Professional Version, v4.8.6.7+), Windows 7.1+, .Net Framework 4.6.1+. Once you got that setup, make a note of the time and begin]

Step 1. Start a new CodeTrigger project within Visual Studio, give it a name TestableWebApiMvc, and select the ‘DB to Multi-tier Mvc ..’ wizard. Make sure you click the sub-option ‘Enable Test’, new in 4.8.6.7. This small, innocuous option heralds significant changes in the generated code, specifically support for mockable repositories etc. Click next.

pr_pr1a

Step 2. Select your data source type, and configure the connection settings. Click ‘Connect’ to verify the settings, and click ‘Create’. Here we are using SQL Server, just to vary things since in the last post we used Oracle (and next post we might use MYSQL). We will be using the Northwind test database, feel free to use any other, but you will need to edit your code when we refer to entities in the northwind database such as ‘Categories’ or ‘Products’.

pr_pr2a

pr_pr3b

Step 3. Voila, the multi-tier Visual Studio project is generated and pre-built. (If you don’t have nuget automatic package download enabled, you may have missing package build errors at this stage, in this case, enable nuget automatic package download on your solution, or otherwise download the missing asp.net core packages from nuget, and rebuild. If you do have nuget automatic package download enabled then this is all done for you).

Note that this wizard will attempt to automatically create the identity tables required by Asp.Net identity, in your database. If those tables already exist then they will not be regenerated.

In the ‘Schema Objects’ tab, select any tables you want to be represented in the final application. For this illustration we use the the Categories and the Products table.

pr_pr3a

Step 4. Click on the ‘Business Objects’ tab. The business model is generated from your previous selections and all related business model entities are displayed for your selection, along with their relationships. Select ‘BOCategories’ and ‘BOProducts ‘, as the objects we will be working with in our tests.

pr_pr4a

Step 5. Shoot! Click the red button to generate the selected code classes. Stand back and be happy as CodeTrigger spews out several thousand lines of required code in a matter of seconds! At this point the project/solution will build itself and report happy. If there’s any outstanding tasks, they might be reported a the ‘Things to do’ tab that sometimes pops up, but I find that useful only when I’ve changed some of the default automatic code generation settings (like automatic sql scripting or automatic project file update).

pr_pr5a

Step 6. Hit F5 to build and run. Enjoy the sight of your fully functioning Asp.Net Mvc App. Login using an auto generated login for debug purposes.

pr_pr8a

Check the time, 5 mins! So far so good. We have a fully functioning basic app, albeit auto-generated. Now we have 10 mins left to setup the unit testing framework.

 

Step 7. Add a Unit Test project to the solution

pr_pr9a

 

pr_pr10a_2

 

 

Add references to the business, data and mvc projects to the unit test project.

Add the System.Net.Http, and System.Web.Http references to the unit project.

Its important to get the right references for System.Web.Http and System.Net.Http.

 

On my system System.Web.Http is the one in the packages folder {project }\packages\Microsoft.AspNet.WebApi.Core.5.2.3\lib\net45 while the System.Net.Http is from C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.6.1

 

Step 8. Create the mock repositories for testing your code without involving the database. Add a folder called MockRepositories to the Unit Test project and add the following classes to it.

MockRepositoryBase.cs


using System;
using System.Collections;
using System.Collections.Generic;
using TestableWebApiMvc.Data.Interfaces;

namespace UnitTestProject1.MockRepositories
{
    public class MockRepositoryBase<T>
    {
        public virtual T New() { return default(T); }
        public virtual IList<T> SelectAll() { return null; }
        public virtual Int32 SelectAllCount() { return 0; }
        public virtual IDictionary<string, IList<object>> SelectAllByCriteriaProjection(IList<IDataProjection> dataProjection, IList<IDataCriterion> listCriterion, IList<IDataOrderBy> listOrder, IDataSkip dataSkip, IDataTake dataTake) { return null; }
        public virtual IList<T> SelectAllByCriteria(IList<IDataCriterion> listCriterion, IList<IDataOrderBy> listOrder, IDataSkip dataSkip, IDataTake dataTake) { return null; }
        public virtual Int32 SelectAllByCriteriaCount(IList<IDataCriterion> listCriterion) { return 0; }
        public virtual INorthwindDb_BaseData BaseData(T tbase) { return null; }
        public INorthwindDb_TxConnectionProvider ConnectionProvider { get { return null; } set { } }
        public Int32 TransactionCount { get; set; }
    }
}

MockCategoriesRepository.cs


using System;
using System.Collections.Generic;
using System.Linq;
using TestableWebApiMvc.Data;
using TestableWebApiMvc.Data.Interfaces;
using TestableWebApiMvc.Business.Repository.Interfaces;

namespace UnitTestProject1.MockRepositories
{
    public class MockCategoriesRepository :  MockRepositoryBase<IDAOCategories>, ICategoriesRepository
    {
        private IList<IDAOCategories> _categoriesList;
        public MockCategoriesRepository(IList<IDAOCategories> categoriesList)
        {   _categoriesList = categoriesList;   }

        public void Insert(IDAOCategories daoCategories) 
        {   _categoriesList.Add(daoCategories); }

        public void Update(IDAOCategories daoCategories) 
        {
            var category = SelectOne(daoCategories.CategoryID);
            category.CategoryName = daoCategories.CategoryName;
            category.Description = daoCategories.Description;
            category.Picture = daoCategories.Picture;
        }

        public void Delete(IDAOCategories daoCategories)
        {   _categoriesList.Remove(SelectOne(daoCategories.CategoryID));    }

        public IDAOCategories SelectOne(Int32? categoryID) 
        {   return _categoriesList.Single((x) => x.CategoryID == categoryID);   }

        public override IList<IDAOCategories> SelectAllByCriteria(IList<IDataCriterion> listCriterion, 
            IList<IDataOrderBy> listOrder, IDataSkip dataSkip, IDataTake dataTake) 
        { 
            //handle search by id
            if (((DataCriterionEq)listCriterion[0]).PropertyName == "CategoryID")
            {
                int categoryID = (int)((DataCriterionEq)listCriterion[0]).PropertyValue;
                return _categoriesList.Where((x) => x.CategoryID == categoryID).ToList();
            }
            return null; 
        }
    }
}

 

Step 9. Write a couple of basic Unit tests. Expand the UnitTest1 class with your unit tests, here are a couple of tests,  GetCategory_ShouldFindCategory , GetCategory_ShouldNotFindCategory 


using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Web.Http.Results;

using Microsoft.VisualStudio.TestTools.UnitTesting;
using UnitTestProject1.MockRepositories;

using TestableWebApiMvc.Data;
using TestableWebApiMvc.Data.Interfaces;
using TestableWebApiMvc.Mvc.SampleApiControllers;
using TestableWebApiMvc.Mvc.SampleViewModels;

using TestableWebApiMvc.Business.Repository;
using TestableWebApiMvc.Business.Repository.Interfaces;
using TestableWebApiMvc.Business;

namespace UnitTestProject1
{
    [TestClass]
    public class UnitTest1
    {
        [TestMethod]
        public async Task GetCategory_ShouldFindCategory()
        {
            CategoriesController catController = new CategoriesController();
            catController.CategoriesRepository = new MockCategoriesRepository(new List<IDAOCategories> {
            new DAOCategories() {CategoryID = 1, CategoryName = "Category One", Description = "First Category"},
            new DAOCategories() {CategoryID = 2, CategoryName = "Category Two", Description = "Second Category"},
            });

            var result = await catController.GetCategories(1) as OkNegotiatedContentResult<CategoriesVm>;
            Assert.IsNotNull(result);
            Assert.AreEqual("Category One", result.Content.CategoryName);
        }

        [TestMethod]
        public async Task GetCategory_ShouldNotFindCategory()
        {
            CategoriesController catController = new CategoriesController();
            catController.CategoriesRepository = new MockCategoriesRepository(new List<IDAOCategories>{
            new DAOCategories() {CategoryID = 1, CategoryName = "Category One", Description = "First Category"},
            new DAOCategories() {CategoryID = 2, CategoryName = "Category Two", Description = "Second Category"},
            });

            var result = await catController.GetCategories(5) as NotFoundResult;
            Assert.IsNotNull(result);
        }
    }
}

Running these tests gives you the following results, ie expected pass, and expected (and handled) fail.

pr_pr11a

 

Step 10. Add some Transaction tests. These tests will test that a couple of operations are submitted successfully as a single transaction, and if an error occurs, then all the operations are rolled back as a single transaction. To run these tests, we will be connecting to the database so we will be using a concrete repository rather than the mocked repository. First thing to do is to add an App.Config file to the Unit Test Project, with the appropriate connection string, you can copy this from the generated Web.config file in the Mvc project.

UnitTestProject1 – App.config



<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<add key="CONNECTION-GUID-FROM-MVC-WEBCONFIG" value="Data Source=MYSERVER;Initial Catalog=Northwind; Connect Timeout=120;Integrated Security=SSPI;Persist Security Info=False;Packet Size=4096" />
</appSettings>
</configuration>

Next, Create a representative webapi controller action with a transactional operation we want to test. In the Mvc project, SampleApiControllers folder, add a class called CategoryProductsController.cs, with the following code:
CategoryProductsController.cs


using System.Net;
using System.Net.Http;
using System.Web.Http;
using System.Web.Http.Description;
using System.Threading.Tasks;
using TestableWebApiMvc.Mvc.SampleViewModels;
using TestableWebApiMvc.Business.Interfaces;
using TestableWebApiMvc.Business.Repository;
using TestableWebApiMvc.Business.Repository.Interfaces;

namespace TestableWebApiMvc.Mvc.SampleApiControllers
{
    public class CategoryProductsController : ApiController
    {
        /*get the relevant repositories from the repository factory*/
        private static RF _rf = RF.New();
        private IProductsRepository _productsRepo = _rf.ProductsRepository;
        private ICategoriesRepository _categoriesRepo = _rf.CategoriesRepository;

        [ResponseType(typeof(void))]
        public async Task<IHttpActionResult> PutCategoryProduct(CategoriesVm vm1, ProductsVm vm2)
        {
            if (!ModelState.IsValid) { return BadRequest(ModelState); }

            var result = Task.Factory.StartNew(() =>
            {
                IUnitOfWork uow = new UnitOfWorkImp(new IRepositoryConnection[] { _categoriesRepo, _productsRepo });
                /*get the domain objects from the viewmodel, and queue them up for transaction*/
                var boCategory = vm1.BOCategories(_categoriesRepo);
                uow.Update(boCategory);

                var boProduct = vm2.BOProducts(_productsRepo);
                uow.Update(boProduct);

                string err;
                /*commit transaction if all ok, rollback if not, and report error*/
                if (!uow.Commit(out err))
                {
                    var resp = new HttpResponseMessage(HttpStatusCode.BadRequest) { Content = new StringContent(err) };
                    throw new HttpResponseException(resp);
                }
                return true;
            });
            await result;
            if (!result.Result)
                return NotFound();

            return StatusCode(HttpStatusCode.NoContent);
        }
    }
}

Next, expand the UnitTest1.cs class by adding the two new tests, plus a couple of support functions:


[TestMethod]
public async Task UpdateCategoryProduct_ShouldCommitTransaction()
{
    /**********************set up the test******************/
    RF _rf = RF.New();
    ICategoriesRepository categoriesRepository = _rf.CategoriesRepository;
    IProductsRepository productsRepository = _rf.ProductsRepository;

    //create a test category and product
    BOCategories testCategory = CreateTestCategory(categoriesRepository);
    BOProducts testProduct = CreateTestProduct(productsRepository, (int)testCategory.CategoryID);
    int testCategoryID = (int)testCategory.CategoryID;
    int testProductID = (int)testProduct.ProductID;
    /***********************end of test setup*****************/

    //run the test, update the category name, and product name
    testCategory.CategoryName = "Updated";
    testProduct.ProductName = "Updated";
    CategoryProductsController categoryProductsController = new CategoryProductsController();
    var result = await categoryProductsController.PutCategoryProduct(new CategoriesVm(testCategory), new ProductsVm(testProduct));

    //check the category name was updated in repository
    BOCategories checkCategory = new BOCategories() { Repository = categoriesRepository };
    checkCategory.Init(testCategoryID);
    StringAssert.Equals(checkCategory.CategoryName, "Updated");

    //check the product name was updated in repository
    BOProducts checkProduct = new BOProducts() { Repository = productsRepository };
    checkProduct.Init(testProductID);
    StringAssert.Equals(checkProduct.ProductName, "Updated");

    /**********tear down the test******************************************/
    testProduct.Delete();
    testCategory.Delete();
    /**********end of test tear down***************************************/
}


[TestMethod]
public async Task UpdateCategoryProduct_ShouldRollbackTransaction()
{
    /**********************set up the test******************/
    RF _rf = RF.New();
    ICategoriesRepository categoriesRepository = _rf.CategoriesRepository;
    IProductsRepository productsRepository = _rf.ProductsRepository;

    //create a test category and product
    BOCategories testCategory = CreateTestCategory(categoriesRepository);
    BOProducts testProduct = CreateTestProduct(productsRepository, (int)testCategory.CategoryID);
    int testCategoryID = (int)testCategory.CategoryID;
    /***********************end of test setup*****************/

    //run the test, update the category name, but also update the product with an invalid categoryid, 
    //so the transaction will be rolled back
    testCategory.CategoryName = "Updated";
    testProduct.CategoryID = 2000000; //invalid category id
    CategoryProductsController categoryProductsController = new CategoryProductsController();

    try
    {
        var result = await categoryProductsController.PutCategoryProduct(new CategoriesVm(testCategory), new ProductsVm(testProduct));
        Assert.Fail();//should not get here.
    }
    catch (System.Web.Http.HttpResponseException ex)
    {
        string errorMsg = ex.Response.Content.ReadAsStringAsync().Result;
        StringAssert.Contains(errorMsg, "FOREIGN KEY");

        //so we know the product update failed. Check the 'Updated' value of category has also been rolled back.
        BOCategories checkCategory = new BOCategories() { Repository = categoriesRepository };
        checkCategory.Init(testCategoryID);
        StringAssert.Equals(checkCategory.CategoryName, "Not Updated");
    }

    /**********tear down the test******************************************/
    testProduct.Delete();
    testCategory.Delete();
    /**********end of test tear down***************************************/
}

private BOCategories CreateTestCategory(ICategoriesRepository categoriesRepository)
{
    BOCategories testCategory = new BOCategories() { Repository = categoriesRepository };
    testCategory.CategoryName = "Not Updated";
    testCategory.Description = "Test Category";
    testCategory.SaveNew();
    return testCategory;
}

private BOProducts CreateTestProduct(IProductsRepository productsRepository, int validCategoryID)
{
    BOProducts testProduct = new BOProducts() { Repository = productsRepository };
    testProduct.ProductName = "New Test Product";
    testProduct.Discontinued = false;
    testProduct.CategoryID = validCategoryID;
    testProduct.SaveNew();
    return testProduct;
}

 

Finally, run the tests and confirm that one transaction is committed successfully, and the other is rolled back successfully.

pr_pr14a

 

Step 11. Check the time. All told, 15 mins? maybe 20? Never mind, time well spent and job well done.

 

Database to full asp.net Mvc App – in 5 minutes

Database to full asp.net Mvc App – in 5 minutes

So you are sitting in one of those interminable tuesday morning meetings at work, trying not to spill your now-cold coffee, wondering if the cryptic doodling on your pad suggests anything about your state of mind, when the project manager says ‘..and I’m sure Nick could convert the database into a fully functional Asp.Net Mvc application, so that we can manage the data in a more user friendly and secure manner. How long do you think that would take, Nick?’.

And now everyone turns to look at you.

Scrambling to figure out what the context is and how the conversation has turned to this, you pluck a hopefully reasonable figure out of the air. ‘Yeah, should be able to turn that around in a couple of weeks..’ you say brightly.

‘Excellent’ says the project manager, ‘I knew i could depend on you’. Everyone smiles in relief, and the meeting then proceeds to plan the complex multi-team delivery of downstream modules, all of it dependent on your proposed timescale.

As you all file out of meeting room 7c, the project manager says ‘..oh by the way, i believe its an Oracle database that we need converting to the Mvc app, and we’ll be needing it to use WebApi, and Asp.Net Identity for secure authentication etc, that’s not going to be a problem is it?’

‘Um..’ you say.

Back at your desk, you are wondering if anyone has ever undertaken to build an asp.net mvc app on an Oracle database, doesn’t sound very likely. How on earth are you going to get off the ground with this, and deploy in 2 weeks?

Let me show you how to do it in 5 minutes.

[You will need Visual Studio (2012+) and the CodeTrigger For Visual Studio extension (Full Trial, or Professional Version, v4.8.6.5+), Windows 7.1+, .Net Framework 4.6.1+. Once you got that setup, make a note of the time and begin]

Step 1. Start a new CodeTrigger project within Visual Studio, give it a name, and select the ‘DB to Multi-tier Mvc ..’ wizard. Click next.

pp1b-pr1

Step 2. Select your data source type, and configure the connection settings. Click ‘Connect’ to verify the settings, and click ‘Create’.

pp2a-pr2

pp11a-pr11

 

Step 3. Voila, the multi-tier Visual Studio project is generated and pre-built. (If you don’t have nuget automatic package download enabled, you may have missing package build errors at this stage, in this case, enable nuget automatic package download on your solution, or otherwise download the missing asp.net core packages from nuget, and rebuild. If you do have nuget automatic package download enabled then this is all done for you).

Note that this wizard will attempt to automatically create the identity tables required by Asp.Net identity, in your database. If those tables already exist then they will not be regenerated.

In the ‘Schema Objects’ tab, select any tables you want to be represented in the final application. If you have 15 tables or less, you might as well click the ‘Select all’ checkbox.

pp3b-pr3

Step 4. Click on the ‘Business Objects’ tab. The business model is generated from your previous selections and all related business model entities are displayed for your selection, along with their relationships. Again, if the number of model entities is about 15 or less, you may as well select all, otherwise choose judiciously according to your project needs.

pp4b-pr4

Step 5. Shoot! Click the red button to generate the selected code classes. Stand back and be happy as CodeTrigger spews out several thousand lines of required code in a matter of seconds! At this point the project/solution will build itself and report happy. If there’s any outstanding tasks, they might be reported in the ‘Things to do’ tab but I find that useful only when I’ve changed some of the default automatic code generation settings (like automatic sql scripting or automatic project file update).

pp5a-pr5

Step 6. Hit F5 to build and run. At this point (or soon after) when I’m building an Oracle based MVC app (as opposed to Sql Server or MySQL), I sometimes find that the solution fails with an unhandled Oracle client Exception. This seems to be because Oracle is still in shock about what just happened. If this occurs I close Visual Studio and re-open it, and that seems to cure the issue once and for all.

Enjoy the sight of your fully functioning Asp.Net Mvc App. Login using an auto generated login for debug purposes (Username: Admin@example.com Password: Admin@123456).

pp6a-pr6

pp6b-pr6

Once logged in, click on the ‘Sample Pages’ link to view/update your various entities.

pp10b-pr10

Step 7. Check the time. 5 minutes! Project manager is still writing up his minutes from the meeting, and you’re already done. What are you going to do with the remaining 13 days, 11 hours and 55 minutes? Well you can get a fresh hot coffee from Inca Beans (no, don’t warm up your old one in the microwave), and sit back. Now if only someone would write a tool that did all the unit, system and integration testing in 5 minutes, you could be home for lunch.

 

Edit 29th June 2016: For a version of this approach which enables unit-testing by using the mockable repository pattern, see Database to Testable WebApi MVC app in 15 mins