[英]Mocking Services in ASP.NET Core
我有一个简单的表单可以保存,然后使用MailKit提供电子邮件通知,其中xUnit和Moq用于单元测试。 我在设置单元测试和相关服务时遇到困难。 我有一个变通办法(在action方法中为'if'语句),仅测试核心回购保存功能,而不测试电子邮件服务。 如果我取出if语句,则单元测试无法访问适当的方法,例如设置Web根路径。 该错误是空异常。 如果我默认使用此值,则会出现其他错误,例如“没有为DbContext配置数据库提供程序”。
有没有更合适的方法来设置这种单元测试? 还是设置单元测试以同时测试Create()和电子邮件功能是错误的,因为它违反了单功能单元测试规则?
单元测试:
[Fact]
public void Can_Create_New_Lesson()
{
//Arrange
//create a mock repository
Mock<IHostingEnvironment> mockEnv = new Mock<IHostingEnvironment>();
Mock<ILessonRepository> mockRepo = new Mock<ILessonRepository>();
Mock<UserManager<AppUser>> mockUsrMgr = GetMockUserManager();
Mock<RoleManager<IdentityRole>> mockRoleMgr = GetMockRoleManager();
var opts = new DbContextOptions<AppIdentityDbContext>();
Mock <AppIdentityDbContext> mockCtx = new Mock<AppIdentityDbContext>(opts);
//create mock temporary data
Mock<ITempDataDictionary> tempData = new Mock<ITempDataDictionary>();
//create the controller
LessonController target = new LessonController(mockRepo.Object, mockEnv.Object, mockUsrMgr.Object, mockRoleMgr.Object, mockCtx.Object)
{
TempData = tempData.Object
};
//create a lesson
Lesson lesson = new Lesson { Title = "Unit Test", Domain= "Unit Test"};
//Act
//try to save the product using the Create method of the controller
IActionResult result = target.Create(lesson);
//Assert
//check that the repository was called
mockRepo.Verify(m => m.SaveLesson(lesson));
//check the result type is a redirection to the List action method of the controller
Assert.IsType<RedirectToActionResult>(result);
Assert.Equal("Success", (result as RedirectToActionResult).ActionName);
}
Create()操作方法:
[HttpPost]
public IActionResult Create(Lesson lesson)
{
if (ModelState.IsValid)
{
repository.SaveLesson(lesson);
//This IF statement is a workaround for the unit test
//don't email users if the Title is "Unit Test"
if (lesson.Title != "Unit Test")
{
emailUsers(lesson);
}
TempData["message"] = $"{lesson.Title} has been saved";
//show the user that the update was made successfully
return RedirectToAction("Success");
}
else
{
//there is a problem with the data values
return View(lesson);
}
}
电子邮件功能:
public void emailUsers(Lesson lesson)
{
var webRoot = environment.WebRootPath;
var filePath = System.IO.Path.Combine(webRoot, "email\\NewLessonSubmitted.txt");
string message = System.IO.File.ReadAllText(filePath);
string domain = lesson.Domain;
IQueryable<AppUser> userList = GetUsersInRole(identityContext, domain);
//if there are users in that domain, send the email
if (userList != null)
{
foreach (AppUser user in userList)
{
sendEmail(domain, message, user.Email);
}
}
}
编辑:正如MotoSV所指出的,我已经将电子邮件服务实现为一个类。 但是,我仍然收到错误消息“尚未为此DbContext配置任何数据库提供程序”异常的堆栈跟踪指向以下方法:
public static IQueryable<AppUser> GetUsersInRole(AppIdentityDbContext db, string roleName)
{
if (db != null && roleName != null)
{
var roles = db.Roles.Where(r => r.Name == roleName);
if (roles.Any())
{
var roleId = roles.First().Id;
return from user in db.Users
where user.Roles.Any(r => r.RoleId == roleId)
select user;
}
}
return null;
}
我的dbContext类中有以下构造函数:
public AppIdentityDbContext(DbContextOptions<AppIdentityDbContext> options)
: base(options) { }
编辑:解决方案(由MotoSV提供)是:1)使用适当的方法创建电子邮件服务类,以及2)为Microsoft.EntityFrameworkCore.InMemory安装适当的Nuget包3)将DbContext模拟为:
var opts = new DbContextOptionsBuilder<AppIdentityDbContext>()
.UseInMemoryDatabase(Guid.NewGuid().ToString())
.Options;
Mock<AppIdentityDbContext> mockCtx = new Mock<AppIdentityDbContext>(opts);
首先, 永远不要做诸如单元代码中的条件代码之类的事情。 如果没有其他原因,那么您违反了单元测试的整个重点,因为测试访问的代码路径与用户实际体验的路径不同; 您这样做不会学到任何东西。
测试回购实际上保存的内容是回购测试的工作, 而不是操作测试。 与您的邮件服务类似:确保实际发送电子邮件应该是对邮件服务的测试,而不是您的操作方法。
总之,这里的测试应仅确保已采取适当的措施(即,点击了回购保存,并且点击了发送电子邮件服务)。 这样,您可以放入简单的模拟程序,这些模拟程序只具有那些可用的方法。 您不需要(也不应该)建立与DB / SMTP服务器的完整连接,因为此时您正在进行集成测试,而不是单元测试。
我将把负责发送电子邮件的代码移到自己的类中。 此类将实现一个接口,然后可以在您的测试中对其进行模拟。
例如,创建接口和实现:
public interface IEmailService
{
void SendEmail(string to, string from, string body);
}
public class EmailService : IEmailService
{
public void SendEmail(string to, string from string body)
{
...
}
}
EmailService
类将包含与MailKit对话所需的功能。 然后,向.NET Core注册IEmailService
并将其添加到您的类的构造函数中:
public class LessonController : Controller
{
private readonly IEmailService _emailService;
public LessonController(IEmailService service, ...)
{
_emailService = emailService;
}
public void emailUsers(Lessong lesson)
{
...
if(userList != null)
{
foreach(...)
{
_emailService.Send(...);
}
}
...
}
}
在测试中,创建一个模拟并将其传递给构造函数。
您的应用程序发送电子邮件类的构造函数应采用“电子邮件提供商”对象,该对象是基于IEmailProvider接口的通用电子邮件抽象,并且/或者还应采用IDataAccessProvider实现。
现在,您可以在测试中模拟这两个接口,并将它们传递给send email类,以仅测试您的实现。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.