发布单元测试EF&;ASP.NET标识-NotSupportedException:模型兼容性

本文关键字:标识 NET -NotSupportedException 兼容性 模型 ASP 单元测试 EF amp | 更新日期: 2023-09-27 17:54:08

背景:

根据我新雇主严格的单元测试要求,我正在努力获得一些单元测试方面的经验,但单元测试作为一个整体对我来说是新的。我在尝试测试任何使用ASP的方法时遇到了很多问题。NET Identity,由于依赖

HttpContext.Current.GetOwinContext().Authentication

HttpContext.Current.GetOwinContext().GetUserManager

现在,到目前为止,这个当前项目还没有完全利用接口和依赖项注入,而是将我们组织的Active Directory系统纳入我们的身份数据库的一部分(当某人使用有效的Active Directory凭据登录时,在Users表中为其创建一行,然后从那时起为其使用身份数据库(,我一直在用Interfaces和Ninject改造我们的Identity方面,并将FakeIseasy添加到我们的测试单元测试项目中。

这确实意味着我们目前正在生成使用实际数据库本身的测试,而不是伪造。我们知道这是一种糟糕的做法,这样做只是为了在我们进行第一个真正的项目时,不要让我们这些新人的头脑过载。我们已经解决了(大部分/全部(这导致的问题,我们的测试完成后会清理干净。

问题:
我在尝试单元测试以下方法时遇到了一个特殊的问题:

public bool ResetPassword(User user)
    {
        if (!user.EmailConfirmed) return false;
        user.RequirePasswordReset = true;
        string randomGeneratedPassword = GenerateRandomPassword(20); // defined at end of class
        user.PasswordHash = hash.HashPassword(randomGeneratedPassword);
        if (!UpdateUser(user)) return false;
        string message = "We have received a request to reset your password. <br /><br />" +
            "Your new password is shown below. <br /><br />" +
            $"Your new password is: <br />{randomGeneratedPassword}<br /><br />" +
            "This password is only valid for one login, and must be changed once it is used. <br /><br /><br /><br />" +
            "Server<br />AmTrust Developer University";
        SendFormattedEmail(user.Email, user.FullName, message, "Your password has been reset");
        return true;
    }

到目前为止,我编写的测试(省略了测试用例(是:

public bool ResetPasswordTests(bool emailConfirmed)
    {
        //arrange
        _user = new User()
        {
            Email = "ttestingly@test.com",
            EmailConfirmed = emailConfirmed,
            FirstName = "Test",
            isActive = true,
            isActiveDirectoryAccount = false,
            LastName = "Testingly",
            PasswordHash = _hash.HashPassword("secret1$"),
            RequirePasswordReset = false,
            UserName = "ttestingly"
        };
        string hashedPass = _user.PasswordHash;
        _identityContext.Users.Add(_user);
        _identityContext.SaveChanges();
        //Suppress the email sending bit!
        A.CallTo(() => _userBusinessLogic_Testable.SendFormattedEmail(null, null, null, null, null))
        .WithAnyArguments()
        .DoesNothing();
        //act
        bool result = _userBusinessLogic_Testable.ResetPassword(_user);
        //assert
        Assert.That(result);
        Assert.That(_user.PasswordHash != hashedPass);
        Assert.That(_user.RequirePasswordReset);
        return result;
    }

运行此测试(针对所有不同的测试用例(会返回以下异常:

系统。NotSupportedException:无法检查模型兼容性,因为数据库不包含模型元数据。只能为使用代码优先或代码优先迁移创建的数据库检查模型兼容性。

这是由_identityContext.Users.Add(_user); 引起的

我所看到的关于这个问题的一切都表明,这是由于在运行代码试图连接到数据库时打开了与数据库的连接(我不认为是这样(,或者是由于试图让EF管理一个预先存在的数据库(事实并非如此:我在测试之间多次删除数据库,试图验证这一点(。

注意:目前我们团队的所有数据库都只是localhost数据库,所以没有其他人干扰我的东西。

我见过一个这样的例子,解决方案是更改连接字符串,但这个问题只发生在单元测试中——我已经验证了在运行时,在我为合并接口、Ninject和Active Directory所做的任何更改之前,应用程序上的一切都能按预期工作——所以我不认为连接字符串本身就是问题所在,但这里有相关的连接字符串
(它们是正确的XML,但我不确定如何让它们在Stack Overflow上正确显示,所以我删除了所有的大括号(:

connectionStrings
add name="ADUUserDB"providerName="System.Data.SqlClient"connectionString="数据源=localhost''sql2014;初始目录=ADUUser数据库;集成安全性=True;连接超时=15;加密=False;TrustServerCertificate=False;MultipleActiveResultSets=True">
/connectionStrings

发布单元测试EF&;ASP.NET标识-NotSupportedException:模型兼容性

我没有关于数据库的信息,但我认为我对A.CallTo有一个想法。FakeIseasy对被要求配置Single(一种扩展方法(感到不满。你应该只是配置Fake。也许

A.CallTo(() => _userBusinessLogic.GetUsers(null, null, null, null, null, null, null))
                                 .WithAnyArguments()
                                 .Returns(new [] { _user });

公平地说,FakeIseasy可以更好地向您指出问题(尽管通常当A.CallTo内部只有一个方法调用时会出现这种情况,所以它更容易判断(。我创建了第786期来追踪这件事。

我发现了这个问题是什么,令人尴尬的是,这是一个简单的配置问题——我实际上并没有伪造我试图测试的BLL正在使用的存储库。我直接跳到数据库上下文,它以前工作过,但由于某种原因,我当时的新的、部分伪造的设置失败了。

因此,我必须做出的更改很简单:
private IUserRepository _userRepository = A.Fake<IUserRepository>();
相关的底层存储库调用是用A.CallTo((伪造的;

这也消除了向数据库中添加内容,然后在测试结束时删除这些内容的需要,而测试正是抛出错误的地方。