3

I have no experience on writting Unit Tests and I am starting with it. My project has a Data Access Layer (with nhibernate) where I have repositories that are injected by an IoC container into some Services in a Business Layer. I want to write Unit Tests to test some methods on the Services (business Layer). I know it is important to have Moqs to simulate the Repositories and avoid hits over the dataBase. I would like to know how to test this method:

public bool AddNewProject(ProjectViewModel viewModel)
{
    // _projectRepository is an interface: IProjectRepository, resolved by IoC container
    if (_projectRepository.ExistsNumber(viewModel.Number))
    {
       Validation.AddError("Number", GlobalResource.DuplicatedNumber);
       return false;
    }

    if (_projectRepository.GetTotalAvailable(viewModel.ItemId) > 0)
    {
       Validation.AddError("ItemId", GlobalResource.NotItemAvailable);
       return false;
    }

    var project = Mapper.Map<Project>(viewModel);

    project.Data = _projectRepository.GetData(viewModel.ItemId);

    _projectRepository.Save(project);

    viewModel.Id = project.Id;

    return true;
}

I know I have to write at least three unit tests, one for each validation and one for success. The problem is that in the AutoMapper configuration, I have my IoC container resolving a type the AfterMap event, for sample:

config.CreateMap<ProjectViewModel, Project>()
      .AfterMap((vm, p) => { 
           // it is necessary because nhibernate will save this relation
           var cityRepository = container.Resolve<ICityRepository>(); 
           p.City = cityRepository.Load(vm.CityId);              
       });

I saw in some place on the web that it is not necessary to have the IoC container in the unit test and I should Moq just the methods I need (not sure if it is correct). Should I have to define memory repositories and register them on a ioc container on the test project? I would be difficult to test because i need to rewrite all the repositories (more than 300).

Obs: It is difficult to change because there are more than 300 entities using Repository.Load to make associations on the AfterMap event on the AutoMapper.

This is my unit test:

[TestMethod]
public void Should_not_save_a_project_by_existent_number()
{
    var viewModel  = new ProjectViewModel();
    viewModel.Code = "PJT";
    viewModel.CityId = 1;
    viewModel.ItemId = 1;
    viewModel.Name = "This is a test";

    // for the validation, I use this simulator in memory (in asp.net mvc I encapsulate the ModelState)
    var validator = new MemoryValidation();
    var projectMoqRepository = new Mock<IProjectRepository>();
    // I will mock methods here...

    var projectService = new ProjectService(projectMoqRepository.Object, validator);

    // the ICityRepository is defined on the IoC, so when I call AddNewProject
    // it will be resolved inside the AutoMapper context on the AfterMap event.
    // it is not injected on the ProjectService ctor.
    var result = projectService.AddNewProject(viewModel);

    projectService.Validation["Number"].ShouldNotBeEmpty();
    result.ShouldBeFalse(); 
}
Karl
  • 31
  • 4
  • 1
    tl;dr; what do you need to mock? – raven Dec 28 '16 at 12:30
  • How does your unit test look like and the constructor of the class you're trying to test? – raven Dec 28 '16 at 12:36
  • configure the mapping specifically for that unit test to not do the after map. mock the repository as ususual and do the test – Nkosi Dec 28 '16 at 12:39
  • You should trust that the IoC container and mapping layer have been tested by their respective developers, and not consider it for your System under Test. Use `FakeDbSet` or something similar to stub out the data you expect to return and test your repository code like that. If you want to test the service layer, Mock out the repository objects and override their methods using `.Returns`. Then just pass them into your service layer constructors as you would when using IoC. – G0dsquad Dec 28 '16 at 12:39
  • Also, if you do have a mapping dependency inside the service layer performing say, Domain Model -> DTO then you can always add the relevant AutoMapper config to a unit test `[Setup]` method. – G0dsquad Dec 28 '16 at 12:44
  • @BenSnaize, thank you for the comments, rewrite the autoMapper configuration is a possible way but we have more than 300 autoMapper configs. The problem is the dependency of the IoC on the autoMapper afterMap event. – Karl Dec 28 '16 at 12:49
  • @BenSnaize should I create a IoC container on the scope of the unit test, register the mocked objects (repositories) and resolve my service on the business layer with this container? i mean, for each unit test should I do this (create a new IoC container)? – Karl Dec 28 '16 at 12:49
  • Definitely don't create an IoC container in a unit test, it's beyond the scope of SuT and therefore no longer a unit test. Just call the service ctor() with your mocked repositories. By doing this you'll also avoid any calls to `AfterMap` and `Load` as you'll be using Mocks. – G0dsquad Dec 28 '16 at 12:52
  • @BenSnaize thank you again Ben for your comments. I understad that creating an AutoMapper configuration on the test level is a possible way, but check my question, I updated it with the Unit Test I am trying with comments on the points, I have my doubt. – Karl Dec 28 '16 at 13:01
  • Is it not possible to allocate the correct `City` to the ViewModel before mapping it to a Domain Model? That way, you could remove the nested dependency. – G0dsquad Dec 28 '16 at 13:12
  • @BenSnaize no, the ViewModel holds the CityId not the City object. The AutoMapper afterMap event is used to define the City property on the domain model object (because nhibernate persists this property). that is the reason I have a definition of the ICityRepository on the AfterMap (resolved by IoC container). – Karl Dec 28 '16 at 13:20
  • Where do you setup AutoMapper? If it's in on AppStart with other config classes, you can easily add these calls to the test class init method. You can then call `AfterMap` and use a Mock `ICityRepository` instead e.g. `p.City = mockCity.Load(vm.CityId);`. Just make sure to Mock out `.Load` to return a `City`. – G0dsquad Dec 28 '16 at 13:59

0 Answers0