I am trying to unit test an Api Controller that asynchronously sends an email to a user based on an HTML template.
The controller action:
public async Task<IHttpActionResult> Email(int id)
{
var company = _unitOfWork.Companies.GetCompany();
var repairTypes = _unitOfWork.RepairTypes.GetRepairTypes().ToList();
var repair = _unitOfWork.Repairs.GetRepair(id);
if (repair == null || company == null || !repairTypes.Any() || !repair.Devices.Any())
return NotFound();
if (repair.Customer.Email == null)
{
return BadRequest("No customer email");
}
try
{
var viewModel = new ReceiptViewModel(company, repair, false, false);
viewModel.AssignRepairTypes(repairTypes);
await Core.Utilities.Email
.SendEmail(repair.Customer.Email, viewModel.GetReceiptEmailBody(), "Your Repair Summary");
}
catch (Exception exception)
{
return BadRequest(exception.Message);
}
return Ok();
}
View model that reads the template from disk:
public class ReceiptViewModel
{
public Company Company { get; set; }
public Repair Repair { get; }
public bool ShowLogo { get; private set; }
public bool AutoPrint { get; private set; }
public ReceiptViewModel(Company company, Repair repair, bool showLogo, bool autoPrint)
{
Company = company;
Repair = repair;
ShowLogo = showLogo;
AutoPrint = autoPrint;
if (Company.LogoPath.Contains("~"))
{
Company.LogoPath = Company.LogoPath.Remove(0, 1);
}
}
public void AssignRepairTypes(IEnumerable<RepairType> repairTypes)
{
Repair.Devices.ForEach(d => d.RepairType =
repairTypes.SingleOrDefault(rt => rt.Id == d.RepairTypeId));
}
public string GetReceiptEmailBody()
{
var server = HttpContext.Current.Server;
// Generate the email body from the template file.
var emailHtmlBody = new TemplateService();
var path = server.MapPath("~/Views/Repairs/ReceiptTemplate.cshtml");
var readAllText = File.ReadAllText(path);
return emailHtmlBody.Parse(readAllText, this, null, "RepairEmail");
}
}
And the test class:
[TestClass]
public class RepairsControllerTests
{
private RepairsController _controller;
private Mock<ICompanyRepository> _mockCompanyRepository;
private Mock<IRepairRepository> _mockRepairRepository;
private Mock<IRepairTypeRepository> _mockRepairTypeRepository;
[ClassInitialize]
public static void Init(TestContext context)
{
Mapper.Initialize(c => c.AddProfile<MappingProfile>());
}
[TestInitialize]
public void TestInitialize()
{
HttpContext.Current = new HttpContext(new HttpRequest(null, "http://test.org", null), new HttpResponse(null));
_mockCompanyRepository = new Mock<ICompanyRepository>();
_mockRepairRepository = new Mock<IRepairRepository>();
_mockRepairTypeRepository = new Mock<IRepairTypeRepository>();
var mockUoW = new Mock<IUnitOfWork>();
mockUoW.SetupGet(u => u.Companies).Returns(_mockCompanyRepository.Object);
mockUoW.SetupGet(u => u.Repairs).Returns(_mockRepairRepository.Object);
mockUoW.SetupGet(u => u.RepairTypes).Returns(_mockRepairTypeRepository.Object);
_controller = new RepairsController(mockUoW.Object);
_controller.MockCurrentUser("1", "user1@domain.com");
}
//All other tests..
[TestMethod]
public async Task Email_ValidRequest_ShouldReturnOk()
{
var company = new Company() { Name = "Test", Email = "a@a.com", Id = 1, City = "T", Country = "C", LogoPath = "", PhoneNumber = "", PostCode = "", Street = "", TnC = "" };
var repairTypes = new List<RepairType>() { new RepairType() { Id = 1, Name = "Test" } };
var devices = new List<Device>() { new Device() { Id = 1, SerialNumber = "1", RepairTypeId = 1 } };
var repair = new Repair
{
Id = 1,
Customer = new Customer() { Email = "test@tm.com" },
Devices = devices
};
_mockRepairRepository.Setup(r => r.GetRepair(1)).Returns(repair);
_mockCompanyRepository.Setup(r => r.GetCompany()).Returns(company);
_mockRepairTypeRepository.Setup(r => r.GetRepairTypes()).Returns(repairTypes);
var result = await _controller.Email(1);
result.Should().BeOfType<OkResult>();
}
}
So the problem is that the Email_ValidRequest_ShouldReturnOk()
test fails because Server.Mappath
is returning null
and therefore the template is not loaded. As you can see, I tried initialising the current HttpContext
but Server.Mappath
is still not working. I have also tried replacing the Server.Mappath
call with Path.Combine
, using the current app domain to determine the path. This also did not work as the app domain was pointing to Debug
folder.
Having done some research on this matter, I realise that using Server.MapPath
is not recommended as it introduces another dependency, which is very difficult to unit test.
I am very new to unit testing and clean architecture in general so any suggestions are greatly appreciated.