繁体   English   中英

ASP.NET Core中的模拟服务

[英]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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM