如何单元测试一个MVC项目,使用实体框架6,Linq,这也是相当依赖于数据库

本文关键字:Linq 框架 数据库 依赖于 实体 单元测试 项目 MVC 一个 | 更新日期: 2023-09-27 18:04:05

我知道这是一个非常广泛的问题,这实际上是我的主要问题。我是单元测试的新手,我不知道在哪里可以找到有用的资源,因为我甚至不确定要搜索什么。我被分配到一个MVC网站项目中添加单元测试,这是我的团队第一次尝试实现这两种方法,但是我们一直使用的旧方法已经过时,没有优化,所以我们正在尝试采用更新和更有效的方法。

新的MVC项目仍然很小,是对一个旧的遗留网站的重写,这个网站是可怕的,难以管理的,每一个小的改变都需要人工测试几个小时才能完成。我们希望构建更好的东西,并相信MVC、实体框架和单元测试是实现这一目标的方法。以下是我如何根据我们研究过的其他MVC项目来设置我们的项目。

这是一个基本的控制器ActionResult…

public ActionResult SignUp()
{
    return View("SignUp");
}
[HttpPost]
public ActionResult SignUp(UserSignUpView userSignUpView)
{
    if (User.Identity.IsAuthenticated)
    {
        return RedirectToAction("Index", "Home");
    }
    else if (!ModelState.IsValid)
    {
        return View(userSignUpView);
    }
    string error = userManagerModel.CheckIfAccountExists(userSignUpView.LoginName, userSignUpView.Email);
    if (error != null)
    {
        ModelState.AddModelError("", error);
        return View("SignUp");
    }
    else
    {
        userManagerModel.AddAccount(userSignUpView);
        FormsAuthentication.SetAuthCookie(userSignUpView.LoginName, false);
        return RedirectToAction("Welcome", "Home");
    }
}

这是一个基本的模型…

public class UserSignUpView
{
    [Key]
    public int UserID { get; set; }
    [Display(Name = "Login ID")]
    [Required(ErrorMessage = "* Required")]
    public string LoginName { get; set; }
    [Display(Name = "Email")]
    [Required(ErrorMessage = "* Required")]
    [EmailAddress(ErrorMessage = "Invalid Email Address")]
    public string Email { get; set; }
    [Display(Name = "Password")]
    [Required(ErrorMessage = "* Required")]
    [RegularExpression(@"^(?=.*[!@#$&*])(?=.*[0-9])(?=.*[a-zA-Z]).{8,20}$",
        ErrorMessage = "Password must be between 8 and 20 characters long. Password must also contain at least 1 upper case letter, 1 number, and 1 special character.")]
    public string Password { get; set; }
    [Display(Name = "Confirm Password")]
    [Required(ErrorMessage = "* Required")]
    [Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
    public string ConfirmPassword { get; set; }
}

下面是与SignUp()操作相关的辅助方法…

    public string CheckIfAccountExists(string loginName, string email)
    {
        using (var db = new Database())
        {
            if (db.Users.Where(o => o.LoginName.Equals(loginName)).Any()
                && db.Users.Where(o => o.Email.Equals(email)).Any())
            {
                return "Both Login and Email already exists";
            }
            else if (db.Users.Where(o => o.LoginName.Equals(loginName)).Any())
            {
                return "Login Name already taken.";
            }
            else if (db.Users.Where(o => o.Email.Equals(email)).Any())
            {
                return "An account with this email already exists.";
            }
        }
        return null;
    }
    public void AddAccount(UserSignUpView userSignUpView)
    {
        using (var db = new Database())
        {
            string securePassword = //method that adds salt and hashes userSignUpView.Password
            User user = new User();
            user.LoginName = userSignUpView.LoginName;
            user.SecurePassword = securePassword;
            user.Email = userSignUpView.Email;
            db.Users.Add(user);
            db.SaveChanges();
        }
    }

所以我试图添加单元测试,从我读到的嘲笑数据库是我最好的选择,但我所有的尝试都失败了。linq、实体框架、模拟数据、mvc和单元测试的资源到处都有,但我还没有幸运地找到2008年以后编写的所有这些东西。是否有一种更好的方法来设置这些部分,使其与单元测试更有凝聚力?我说了这么多,听起来可能像小孩子在抓稻草,但我们只是想摆脱我们长期以来一直坚持的可怕的传统做法。

TLDR;有任何体面的和最新的教程单元测试的MVC实体框架?

如何单元测试一个MVC项目,使用实体框架6,Linq,这也是相当依赖于数据库

给出简单的控制器示例,如何使该控制器更易于测试?

作为初学者,你应该考虑抽象掉实现依赖。

这意味着抽象userManagerModelFormsAuthentication。(尽量避免与静态类耦合)

public interface IUserManager {
    string CheckIfAccountExists(string loginName, string email);
    void AddAccount(UserSignUpView userSignUpView);
}
public interface IAuthenticationService {
    void SetAuthCookie(string userName, bool createPersistentCookie);
}

,这样当你想测试控制器的动作时,你可以用假的/模拟的实现来替换它。

public class AccountController : Controller {
    IUserManager userManagerModel;
    IAuthenticationService formsAuthentication;
    public AccountController(IUserManager userManagerModel, IAuthenticationService formsAuthentication) {
        this.userManagerModel = userManagerModel;
        this.formsAuthentication = formsAuthentication;
    }
    public ActionResult SignUp() {
        return View();
    }
    [HttpPost]
    public ActionResult SignUp(UserSignUpView userSignUpView) {
        if (User.Identity.IsAuthenticated) {
            return RedirectToAction("Index", "Home");
        } else if (!ModelState.IsValid) {
            return View(userSignUpView);
        }
        string error = userManagerModel.CheckIfAccountExists(userSignUpView.LoginName, userSignUpView.Email);
        if (error != null) {
            ModelState.AddModelError("", error);
            return View(userSignUpView);
        } else {
            userManagerModel.AddAccount(userSignUpView);
            formsAuthentication.SetAuthCookie(userSignUpView.LoginName, false);
            return RedirectToAction("Welcome", "Home");
        }
    }
}

看起来几乎和你原来的一样,但是你会注意到userManagerModel是一个接口,而不是一个具体的类。

然后你可以手动或使用你选择的mock框架创建假/mock来测试控制器的功能,而不需要控制器与数据库的紧密耦合。

您将确保您的生产类实现接口并提供生产中期望的功能。

例如

public class FormsAuthenticationService : IAuthenticationService {
    public void SetAuthCookie(string userName, bool createPersistentCookie) {
        FormsAuthentication.SetAuthCookie(userName, createPersistentCookie);
    }
}