Patrick
Patrick

Reputation: 2781

Mstest Controller and Service Layer With Dependency Injection (Autofac) and Automapper

I'm trying to bring test layer to my project but I'm not getting there :( hope someone can help me.

Controller (based on Automapper mapping and Dependency Injection Container):

    public virtual ActionResult SearchCategories(string keywords)
    {
        var result = _categoryService.SearchCategories(keywords);

        var resultViewModel = Mapper.
            Map<IList<SearchCategoriesDto>, 
                IList<SearchCategoriesViewModel>>(result);

        return View(resultViewModel);
    }    

Service Layer:

   public IList<SearchCategoriesDto> SearchCategories(String keywords)
    {
        // Find the keywords in the Keywords table
        var keywordQuery = _keywordRepository.Query;

        foreach (string keyword in splitKeywords)
        {
            keywordQuery = keywordQuery.Where(p => p.Name == keyword);
        }

        // Get the Categories from the Search
        var keywordAdCategoryQuery = _keywordAdCategoryRepository.Query;
        var categoryQuery = _categoryRepository.Query;

        var query = from k in keywordQuery
                    join kac in keywordAdCategoryQuery on k.Id equals kac.Keyword_Id
                    join c in categoryQuery on kac.Category_Id equals c.Id
                    select new SearchCategoriesDto
                    {
                        Id = c.Id,
                        Name = c.Name,
                        SearchCount = keywordAdCategoryQuery
                             .Where(s => s.Category_Id == c.Id)
                             .GroupBy(p => p.Ad_Id).Count(),
                        ListController = c.ListController,
                        ListAction = c.ListAction
                    };

        var searchResults = query.Distinct().ToList();

        return searchResults;
    }

Test maded but not working:

    [TestMethod]
    public void Home_SearchCategories_Test()
    {
        // Setup
        var catetoryService = new CategoryService(
                                   _categoryRepository, 
                                   _keywordRepository, 
                                   _keywordAdCategoryRepository);

        // Act
        var result = catetoryService.SearchCategories("audi");

        // Add verifications here
        Assert.IsTrue(result.Count > 0);
    }

Thanks.

Upvotes: 0

Views: 1227

Answers (2)

Patrick
Patrick

Reputation: 2781

Solution to build an Integration test for a Service (in this case, Category Service), using Autofac, Automapper (not necessary in this Service but if it would be necessary, you would need to put in the TestInitialize method as you can see in the coment line in the following solution) and Entity Framework with Daniel J.G. help (thanks Daniel):

First of all I created a separated Test Project using MSTest (only because there is a lot of documentation about it).

Second you need to put the connection string for the Entity Framework where the test data is:

<connectionStrings>
    <add name="DB" connectionString="Data Source=.\sqlexpress;Database=DBNAME;UID=DBUSER;pwd=DBPASSWORD;MultipleActiveResultSets=True;" providerName="System.Data.SqlClient" />
</connectionStrings>

In the < configuration > section after the < / configSections >

Third you create the class for the test:

namespace Heelp.Tests
{
    [TestClass]
    public class CategoryServiceIntegrationTest
    {
        // Respositories dependencies
        private IRepository<Category> _categoryRepository;
        private IRepository<Keyword> _keywordRepository;
        private IRepository<KeywordAdCategory> _keywordAdCategoryRepository;

        // Service under test: Category Service
        private CategoryService _categoryService;

        // Context under test: HeelpDB Connection String in app.config
        private HeelpDbContext db;

        [TestInitialize]
        public void InitializeBeforeRunningATest()
        {
            // IoC dependencies registrations
            AutofacConfig.RegisterDependencies();

            // HERE YOU CAN CALL THE AUTOMAPPER CONFIGURE METHOD
            // IN MY PROJECT I USE AutoMapperConfiguration.Configure();  
            // IT'S LOCATED IN THE App_Start FOLDER IN THE AutoMapperConfig.cs CLASS
            // CALLED FROM GLOBAL.ASAX Application_Start() METHOD

            // Database context initialization
            db = new HeelpDbContext();

            // Repositories initialization
            _categoryRepository = new Repository<Category>(db);
            _keywordRepository = new Repository<Keyword>(db);
            _keywordAdCategoryRepository = new Repository<KeywordAdCategory>(db); 

            // Service initialization
            _categoryService = new CategoryService(_categoryRepository,
                                                   _keywordRepository,
                                                   _keywordAdCategoryRepository);
        }

        [TestCleanup]
        public void CleanDatabaseResources()
        {
            // Release the Entity Framework Context for other tests that will create a fresh new context.
            // With this method, we will make sure that we have a fresh service and repositories instances on each test. 
            db.Dispose();
        }

        [TestMethod]
        public void Home_SearchCategories_Test()
        {
            // Arrange
            var keywords = "audi";

            // Act (the _categoryService instance was created in the initialize method)
            var result = _categoryService.SearchCategories(keywords);

            // Assert
            Assert.IsTrue(result.Count > 0);
        }
    }

}

Now you just have to run the test to see if it passes.

To garantee integration tests, I would recomend a second database identical from the original/production database in terms of tables, but with only your test data.

This will ensure that the tests results will remain the same based on your test data.

The only drawback is that you will need to keep sincronized the tables, but you can use SQL Admin Studio Freeware Tool from Simego to achieve that.

Regards.

Upvotes: 0

Daniel J.G.
Daniel J.G.

Reputation: 34992

I am assuming you want to create an integration test for your category service, using real repositories and database. (as oposed to an unit test where you would use stub\mocks for those repositories and test the service class in isolation)

So you would have a seaparated test assembly where you will add your integration tests, for example having a class for the integration tests at the service level. On that class you will then create instances of all the repositories and the CategoryService before running each test, on a method with the attribute [TestInitialize]. (A method with this attribute will be run by msTest before each test)

As you are also truly working with the database you would like to be sure that any resources used are disposed. For example, disposing an entity framework context. In that case you would add a method with an attribute [TestCleanup] where you will perform any cleanup logic needed. (A method with this attribute will be run by msTest after each test)

With those methods you will make sure you have a fresh service and repositories instances on each test. Then you will implement each individual integration test following the AAA pattern (Arrange, Act, Assert)

So an example of your integration test class with that single test may look like:

public class CategoryServiceIntegrationTest
{
    //dependencies of your class under test
    private ICategoryRepository _categoryRepository; 
    private IKeywordRepository _keywordRepository;
    private IKeywordAdCategoryRepository _keywordAdCategoryRepository;

    //your class under test
    private CategoryService _categoryService;

    [TestInitialize]
    public void InitializeBeforeRunningATest()
    {
        //manually create instances of the classes implementing the repositories
        //I don´t know about how they are implemented but I guess
        //you would need to provide the name of a connection string in the config file (in that case this should be in a config file of the test project)
        //,the connection string itself
        //or maybe you need to initialize an entity framework context
        _categoryRepository = new CategoryRepository( /*whatever you need to provide*/);
        _keywordRepository = new KeywordRepository( /*whatever you need to provide*/);
        _keywordAdCategoryRepository = new KeywordAdCategoryRepository( /*whatever you need to provide*/);

        //Create the class under test with all repositories dependencies          
        //as it is an integration test, they are your real objects and not mocks\stubs
        _categoryService = new CategoryService(_categoryRepository, 
                                               _keywordRepository,
                                               _keywordAdCategoryRepository);
    }

    [TestCleanup]
    public void CleanDatabaseResources()
    {
        //just in case you need to do something like disposing an EF context object          
    }

    [TestMethod]
    public void Home_SearchCategories_Test()
    {
        // Arrange
        var keywords = "audi";

        // Act (the _categoryService instance was created in the initialize method)
        var result = _categoryService.SearchCategories(keywords);

        // Assert
        Assert.IsTrue(result.Count > 0);
    }
}

Upvotes: 1

Related Questions