How to properly mock extension methods with generics in xUnit? - c#

So I'm trying to mock a service of mine, here's the real code:
public class PhaseService : IPhaseService
{
private readonly IRepository<Phase> _phaseRepository;
private readonly IMapper _mapper;
private readonly HrbContext _context;
public PhaseService(IRepository<Phase> phaseRepository, IMapper mapper, HrbContext context)
{
_phaseRepository = phaseRepository;
_mapper = mapper;
_context = context;
}
public async Task<PhaseDto> GetAsync(Guid id)
{
var result = await _phaseRepository.GetActiveAsync(id);
return _mapper.Map<PhaseDto>(result);
}
}
It uses an Extension Method, this is here:
namespace HRB_Server.Application.Extensions
{
public static class RepositoryExtensions
{
/// <summary>
/// Returns the entity to which the given id is a match (no navigation properties loaded). Throws exceptions if the entity is not found or if is not active.
/// </summary>
public static async Task<T> GetActiveAsync<T>(this IRepository<T> repo, Guid id)
where T : BaseEntity
{
T entity = await repo.GetAsync(id);
if (entity == null)
{
throw new EntityNotFoundException(typeof(T), id);
}
if (!entity.IsActive)
{
throw new EntityNotActiveException(typeof(T), id);
}
return entity;
}
}
Here's my xUnit test:
namespace HRB_Server.Tests.Services
{
public class PhaseServiceTest
{
private readonly Mock<IRepository<Phase>> _repository;
private readonly Mock<IMapper> _mapper;
private readonly Mock<HrbContext> _context;
public PhaseServiceTest()
{
_repository = new Mock<IRepository<Phase>>();
//_mapper = new Mock<IMapper>();
_mapper = null;
//_context = new Mock<HrbContext>(new DbContextOptions<HrbContext>(), new HttpContextAccessor());
_context = null;
}
[Fact]
public void GetPhase_ActivePhaseObject_PhaseShouldExist()
{
// Arrange
var newGuid = Guid.NewGuid();
var phase = GetSamplePhase(newGuid);
_repository.Setup(x => RepositoryExtensions.GetActiveAsync<Phase>(_repository, It.IsAny<Guid>()))
.Returns(GetSamplePhase(newGuid));
var phaseService = new PhaseService(_repository.Object, _mapper.Object, _context.Object);
// Act
var result = phaseService.GetAsync(newGuid);
// Assert (expected, actual)
Assert.Equal(phase.Result.Id, newGuid);
}
}
The error I'm getting is in the Setup of the _repository:
repository.Setup(x => RepositoryExtensions.GetActiveAsync<Phase>(_repository, It.IsAny<Guid>()))
.Returns(GetSamplePhase(newGuid));
It says it cannot convert the Mocked repository to a real one, but shouldn't I use the mocked repository here?
What I'm trying to achieve is testing my REAL service and mocking the repository, right? Am I doing it correctly here?

Assuming you are using MOQ, do not try to mock the extension method.
Since you control the code of the extension method then mock a safe path through the extension method.
The extension uses GetAsync in this case and that is what needs to be mocked assuming that is not an extension as well.
//...
_repository
.Setup(x => x.GetAsync(It.IsAny<Guid>()))
.ReturnsAsync(GetSamplePhase(newGuid));
//...
It will allow the test when exercised to go through GetActiveAsync code and if it fails, also throw the Exceptions etc as described in the code.

Related

XUnit mocking a method but doesn't return correct result

I should be getting a list of Product Types but GetProductTypesAsync() returns a null.
Is the class ProductTypeRepo meant to be mocked since it calls the acutal API.
Anyone able to assist?
namespace UnitTest.Service
{
public class ProductTypeServiceTests
{
private readonly IServiceProvider _serviceProvider;
private readonly Mock<IProductTypeRepo> _productTypeRepoMock;
private readonly Mock<ILogger> _LoggerMock
private IProductTypeService _productTypeService;
public ProductTypeServiceTests()
{
_productTypeRepoMock = new Mock<IProductTypeRepo>();
_LoggerMock= new Mock<ILogger>();
_productTypeService = new ProductTypeService(_productTypeRepoMock.Object, _LoggerMock.Object);
}
[Fact]
public async Task GetProductType_ReturnOKStatusCode()
{
var serviceResponse = await _productTypeService.GetProductTypesAsync();
Assert.Equal(
expected: serviceResponse,
actual: serviceResponse
);
}
}
}
--
namespace Service.ProductType
{
public class ProductTypeService : IProductTypeService
{
private readonly IProductTypeRepo _repository;
private readonly ILogger _Logger;
public ProductTypeService(IProductTypeRepo repository, ILogger logger)
{
_repository = repository;
_logger = logger;
}
public async Task<List<Domain.DTO.ProductTypeResponse>> GetProductTypesAsync()
{
var productTypes = await _repository.GetProductTypesAsync();
if (productTypes == null)
{
throw new ProductTypeNotFoundException($"No Product Types were retrieved");
}
return productTypes;
}
}
}
xxxxxxxxxx xxxxxxxx xxxxxx xxxxx
Nowhere in the test is the mock configured to return anything when invoked.
//...
[Fact]
public async Task GetProductType_ReturnOKStatusCode() {
//Arrange
List<Domain.DTO.ProductTypeResponse> expected = new List<Domain.DTO.ProductTypeResponse>();
//..add items to expected list if necessary
_productTypeRepoMock
.Setup(_ => _.GetProductTypesAsync()) //<-- when this is invoked
.ReturnsAsync(expected); //<-- return something.
//Act
List<Domain.DTO.ProductTypeResponse> actual = await _productTypeService.GetProductTypesAsync();
//Assert
Assert.Equal(expected, actual);
}

Mock Repository With Expected Object Using ReturnsAsync Returns Null

I'm creating a random object that I'm expecting back from the test, and using the ReturnsAsync method when setting up the mock repo. The controller inside the Unit Test is returning null, however.
The main difference between my code and a lot of the others is that I'm using AutoMapper to return the DTO object in my controller as an OK object. I'm not sure if that's causing my issue.
Controller Instantiation
private readonly ISuperHeroRepo _repo;
public IMapper _mapper { get; }
public SuperHeroController(ISuperHeroRepo repo, IMapper mapper)
{
_repo = repo;
_mapper = mapper;
}
Repo Method
public async Task<SuperHero> GetSuperHero(int id)
{
return await _db.SuperHeros.FindAsync(id);
}
Mapper Profile
public class SuperHeroProfiles : Profile
{
public SuperHeroProfiles()
{
//Source -> Target
CreateMap<SuperHero, SuperHeroReadDTO>()
.ForMember(target => target.LegalName, option => option.MapFrom(source => $"{source.LegalFirstName} {source.LegalLastName}"))
.ForMember(target => target.Jurisdiction, option => option.MapFrom(source => $"{source.JurisdictionCity}, {source.JurisdictionState}"));
CreateMap<SuperHero, SuperHeroDTO>();
}
}
Controller Method
[HttpGet("{id}")]
public async Task<ActionResult<SuperHeroReadDTO>> Get(int id)
{
var hero = await _repo.GetSuperHero(id);
if (hero == null)
{
return NotFound("Hero Not Found");
}
else
{
// This is where it might be breaking
return Ok(_mapper.Map<SuperHeroReadDTO>(hero));
}
}
Unit Test
// Instantiated
readonly Mock<ISuperHeroRepo> repoMock = new();
readonly Mock<IMapper> mapperMock = new();
private readonly Random random = new();
........
[Fact]
public async Task Get_WithExistingHero_ReturnsExpectedSuperHero()
{
// Arrange
SuperHero expected = CreateRandomSuperHero();
repoMock.Setup(repo => repo.GetSuperHero(It.IsAny<int>()))
.ReturnsAsync(expected);
var controller = new SuperHeroController(repoMock.Object, mapperMock.Object);
// Act - This returns null. Debugging in the controller, I get the object back from repo
var result = await controller.Get(random.Next());
// Assert
Assert.IsType<SuperHeroReadDTO>(result.Value);
var dto = result.Value;
Assert.Equal(expected.SuperHeroName, dto.SuperHeroName);
Assert.Equal($"{expected.LegalFirstName} {expected.LegalLastName}", dto.LegalName);
Assert.Equal($"{expected.JurisdictionCity}, {expected.JurisdictionState}", dto.Jurisdiction);
}
Edited: Added the controller instantiation, and the Mapper Profile. Not sure if it'll add more info.
#shree.pat18 was indeed correct. I did end up needing to set up the Mapper object in the Unit Test and passing it back into the controller. I'll need to refactor, but here below is what resolved it:
[Fact]
public async Task Get_WithExistingHero_ReturnsExpectedSuperHero()
{
// Arrange
SuperHero expected = CreateRandomSuperHero();
repoMock.Setup(repo => repo.GetSuperHero(It.IsAny<int>()))
.ReturnsAsync(expected);
// Setup of the mock Mapper
var mockMapper = new MapperConfiguration(cfg =>
{
cfg.AddProfile(new SuperHeroProfiles());
});
var mapper = mockMapper.CreateMapper();
var controller = new SuperHeroController(repoMock.Object, mapper);
// Act
var result = await controller.Get(random.Next());
// Assert
Assert.IsType<SuperHeroReadDTO>(result.Value);
var dto = result.Value;
Assert.Equal(expected.SuperHeroName, dto.SuperHeroName);
Assert.Equal($"{expected.LegalFirstName} {expected.LegalLastName}", dto.LegalName);
Assert.Equal($"{expected.JurisdictionCity}, {expected.JurisdictionState}", dto.Jurisdiction);
}

C# xUnit Test error - The following constructor parameters did not have matching fixture data

I have implemented a unit test on my ASP.NET Core MVC project using xUnit. When I try to run the test, it gave me an error as below:
The following constructor parameters did not have matching fixture data: Status status"
Below is my code:
IStatusService:
public interface IStatusService
{
Task<StatusIndexViewModel> GetStatusAsync();
}
StatusService:
public class StatusService : IStatusService
{
private readonly DbContext dbContext;
private readonly IMapper mapper;
public StatusService(DbContext dbContext, IMapper mapper)
{
this.dbContext = dbContext;
this.mapper = mapper;
}
public async Task<StatusIndexViewModel> GetStatusAsync()
{
var model = await dbContext
.Status.AsNoTracking()
.ProjectTo<StatusViewModel>(mapper.ConfigurationProvider)
.ToListAsync();
var vm = new StatusIndexViewModel
{
Statuses = model
};
return vm;
}
}
Here is the Controller:
public class StatusController : Controller
{
private readonly IStatusService statusService;
public StatusController(IStatusService statusService)
{
this.statusService = statusService;
}
public async Task<IActionResult> Index()
{
var model = await statusService.GetStatusAsync();
return View(model);
}
}
Below is my unit test class:
public class StatusUnitTest
{
StatusController StatusControllerTest;
private Mock<IStatusService> statusService;
private List<Status> statuses;
private Status status;
public StatusUnitTest(Status status)
{
this.status = status;
statusService = new Mock<IStatusService>();
statusService.Setup(p =>
p.GetStatusAsync()).ReturnsAsync(status);
StatusControllerTest = new StatusController(statusService.Object);
}
[Fact]
public async Task GetStatusByIdNo()
{
var result = await StatusControllerTest.Index();
var viewResult = Assert.IsType<ViewResult>(result);
Assert.IsAssignableFrom<IEnumerable<Status>>
(viewResult.ViewData.Model);
}
}
May I know what mistake I made? How I can test the controller and the service layer? Please give me a guide.

How to mock Automapper (IMapper) in controller

I am trying to write a unit test for my existing MVC Web Aplication. In that I am facing some problem in automapper (IMapper) Whenever am using map function it returns null value.
My Controller Code:
public class UserAdministrationController : BaseController
{
private readonly iUserService _userService;
private readonly IMapper _mapper;
public NewsController(iUserService userService, IMapper mapper)
{
_userService = userService;
_mapper = mapper;
}
public ActionResult Create(int CompanyID == 0)
{
UserDetail data = _userService(CompanyID);
var Modeldata = _mapper.Map<UserDetailViewModel, UserDetail>(data);
return View(Modeldata);
}
}
Mock Mapping Code:
public class MappingDataTest : CommonTestData
{
public Mock<IMapper> MappingData()
{
var mappingService = new Mock<IMapper>();
UserDetailViewModel interview = getUserDetailViewModel(); // get value of UserDetailViewModel
UserDetail im = getUserDetail(); // get value of UserDetails
mappingService.Setup(m => m.Map<UserDetail, UserDetailViewModel>(im)).Returns(interview);
mappingService.Setup(m => m.Map<UserDetailViewModel, UserDetail>(interview)).Returns(im);
return mappingService;
}
}
Mocking Code:
[TestClass]
public class UserAdminControllerTest
{
private MappingDataTest _common;
[TestInitialize]
public void TestCommonData()
{
_common = new MappingDataTest();
}
[TestMethod]
public void UserCreate()
{
//Arrange
UserAdministrationController controller = new UserAdministrationController(_common.mockUserService().Object, _common.MappingData().Object);
controller.ControllerContext = _common.GetUserIdentity(controller);
// Act
ViewResult newResult = controller.Create() as ViewResult;
// Assert
Assert.IsNotNull(newResult);
}
}
Mapper is not working its always showing the null value in controller. kindly help me. Thanks in Advance.
I would recommend not mocking AutoMapper. There's not much value in controller unit tests for one, and this is similar to mocking a JSON serializer. Just use the real thing.
You should try the following:
public class MappingDataTest : CommonTestData
{
public Mock<IMapper> MappingData()
{
var mappingService = new Mock<IMapper>();
UserDetail im = getUserDetail(); // get value of UserDetails
mappingService.Setup(m => m.Map<UserDetail, UserDetailViewModel>(It.IsAny<UserDetail>())).Returns(interview); // mapping data
mappingService.Setup(m => m.Map<UserDetailViewModel, UserDetail>(It.IsAny<UserDetailtViewModel>())).Returns(im); // mapping data
return mappingService;
}
}
The thing is, your mock was expecting the exact instance of UserDetailViewModel interview = getUserDetailViewModel(); to setup this mapping, and this is why it was returning null. Null it will be expecting any reference to UserDetailViewModel and for any reference to UserDetailtViewModel it will return the expected mapped instance.

How do you unit test a MVC controller with EF SaveChanges

Below is a controller with a Post Method. How do i write a unit-test against the CREATE method without saving the changes to the database?
I am trying to write test against my controllers so that when other developers change the code it will not break my functionality ( i have a little bit of functionality on the Create method to keep it simple for now).
public class AdministratorController : Controller
{
private IUnitOfWork _uow;
[HttpPost]
public ActionResult Create(MyModel model)
{
ViewBag.id = model.Id;
if (model.FirstName == model.LastName)
{
ModelState.AddModelError("", "Cannot have same first name and last name.");
}
if (ModelState.IsValid)
{
MyClass record = new MyClass();
record.SAFirstName = model.FirstName;
record.SALastName = model.LastName;
record.SATitle = model.Title;
record.SAEmail = model.EmailAddress;
record.Since = DateTime.Now;
_uow.AdministratorRepository.AddRecord(record);
_uow.SaveChanges();
return RedirectToAction("Index", "Administrator");
}
return View(model);
}
}
2.) my UOW looks something like this:
public class UnitOfWork : IUnitOfWork
{
private readonly MasterContext _context;
public UnitOfWork(MasterContext context)
{
_context = context;
}
public UnitOfWork()
{
_context = new MasterContext();
}
public void SaveChanges()
{
_context.SaveChanges();
}
private IAdministratorRepository _Repo;
public IAdministratorRepository AdministratorRepository
{
get
{
if (this._Repo == null)
{
this._Repo = new IAdministratorRepository(_context);
}
return _Repo;
}
}
3) And my AdministratorRepository constructor looks like:
private readonly MasterContext _context;
public AdministratorRepository(MasterContext context)
{
_context = context;
}
You need to be able to inject a fake/mock IUnitOfWork into your controller. The simplest way to do that is to create an internal constructor on the controller that takes the fake object and create a property that either creates a new instance or returns the existing one.
private IUnitOfWork _uow;
private IUnitOfWork UnitOfWork
{
get
{
_uow = _uow ?? new UnitOfWork();
return _uow;
}
}
public AdministratorController() {}
internal AdministratorController( IUnitOfWork uow )
{
_uow = uow;
}
You'll also need to modify the AssemblyInfo file for the MVC project to make the internal constructor visible to the unit test project. Look up the InternalsVisibleToAttribute for that.
Now in the unit test you can create your fake/mock object and inject it. You don't indicate what mocking framework you're using. We use FakeItEasy, so it would be something like:
var uow = A.Fake<IUnitOfWork>();
var controller = new AdministratorController( uow );
A.CallTo( () => uow.SaveChanges() ).MustHaveHappened();
You should mock your dependencies.In your example AddRecord() of your repository.And after that you should test the returning model fields with your expected model(that you have to set in your unit testing method).

Categories