如何在使用Membership时测试ASP.NET MVC 3身份验证

8

我希望测试我的AccountController。问题是在注册方法中,我使用下一行来创建用户:

Membership.CreateUser(model.Email, model.Password, model.Email, null, null, true, null, out createStatus);

在我的Web应用程序中,我使用一个CustomMembershipProvider,并在web.config文件中进行设置。在我的单元测试Membership类中,我使用标准的SqlMembershipProvider而不是我在应用程序中使用的CustomMembershipProvider
如何在单元测试环境中设置成员身份验证?我的意思是像asp net读取web.config文件后那样以编程方式进行设置。
我已经使用接口来模拟用户管理数据层,但我在考虑是否有一种方法可以避免在这种情况下使用接口。能够在单元测试中设置成员身份验证的模拟实现。
public void RegisterTest()
{
    IUsersManager repository = new Tests.Data.UsersManager();
    AccountController target = new AccountController(repository); 
    //be able to set Membership underlying provider
    Membership.Provider = new MockMembershipProvider();
}
3个回答

8

定义一个会员接口,类似于这样:

public interface IMembershipProvider
{
    void CreateUser(string username, string password);
}

你可以像这样为你的MVC应用程序实现它:

...将其实现到你的MVC应用程序中:

public class AspDotNetMembershipProvider : IMembershipProvider
{
    public void CreateUser(string username, string password)
    {
        string createStatus;

        Membership.CreateUser(
            username,
            password,
            username,
            null,
            null,
            true,
            null,
            out createStatus);

        // throw an exception if createStatus isn't as expected
    }
}

然后将其注入到您的控制器中,并按以下方式使用:

public class AccountController
{
    private readonly IMembershipProvider _membershipProvider;

    public AccountController(IMembershipProvider membershipProvider)
    {
        this._membershipProvider = membershipProvider;
    }

    public ActionResult Register(RegistrationModel model)
    {
        // Try and catch this, returning a success ActionResult if it worked:
        this._membershipProvider.CreateUser(model.Email, model.Password);
    }
}

ASP.NET使用像Membership这样的静态类来处理许多事情,但是静态类访问总是使单元测试变得困难。标准解决方案是为服务定义一个接口,使用静态ASP.NET类来实现它,并将其注入到控制器中。

您可以通过使用默认的DependencyResolver和像Unity这样的DI容器来设置注入(如果尚未设置)。


1
AspDotNetMembershipProvider 应该实现接口吗?像这样 public class AspDotNetMembershipProvider : IMembershipProvider { ... - marapet
Membership类是静态的,但底层提供程序由ASP运行时设置...我在想我也可以设置它。 - Radu D
我的观点是,从您的 AccountController 直接访问静态Membership类不是最优的设计决策;如果您已经在使用仓储的注入,我不确定为什么您想要避免对其他依赖项进行注入?正如 @PHeiberg 所提到的,通过 Microsoft 的 Membership 系统运行测试使它们成为了集成测试而不是单元测试,并且是不必要的;Microsoft 已经为您测试了那部分内容。没有注入就依赖于 Membership 的控制器会产生隐藏的依赖关系,您应该始终避免这种情况。 - Steve Wilkes

2
我已经创建了一个更加结构化的会员提供程序,将其拆分为三个不同的接口(并且该提供程序正在使用DependencyResolver进行解决)。
这使得提供程序易于单元测试。只需测试您对IAccountRepository的实现即可。
您可以在此处阅读有关此内容的信息:http://blog.gauffin.org/2011/09/a-more-structured-membershipprovider/ 或者只需安装NuGet包:
install-package griffin.mvccontrib

0

你的控制器的单元测试不应该使用你的CustomMembershipProvider或标准SQL提供程序。如果进行单元测试,应该使用一个存根/虚拟对象,以完全控制其返回结果。如果在测试中使用真实的提供程序,则不再是单元测试,而是集成测试。

为了实现单元测试能力,你需要定义一个虚拟的提供程序,可以使用模拟框架或手动实现带有“预设”结果的虚拟对象。虚拟对象可能如下所示:

public class FakeMembershipProvider : MembershipProvider
{
   public MembershipCreateStatus CreateStatus = MembershipCreateStatus.Success;

   public void CreateUser((string username, string password, string email, 
       string passwordQuestion, string passwordAnswer, bool isApproved, 
       object providerUserKey, out MembershipCreateStatus status)
   {
      status = CreateStatus;
   }

   ...
}

让控制器将提供程序作为构造函数参数

public class AccountController
{
    private readonly MembershipProvider _membershipProvider;

    public AccountController(MembershipProvider membershipProvider)
    {
        _membershipProvider = membershipProvider;
    }

    public ActionResult Register(RegistrationModel model)
    {
        MembershipCreateStatus result;
        _membershipProvider.CreateUser(model.Email, model.Password, ..., out result);

        return View(/*Make this depend on the result*/);
    }
}

在单元测试中,您希望根据要测试的内容设置虚拟对象,并且您可以针对每个注册结果断言您期望的结果。
[Test]
void Should_display_success_view_when_user_successfully_created()
{
    var membershipProvider = new FakeMembershipProvider();
    membershipProvider.CreateStatus = MembershipCreateStatus.Success;
    var controller = new AccountController(membershipProvider);
    var model = new RegistrationModel();

    var result = controller.Register(model) as ViewResult;

    Assert.That(result.Name, Is.EqualTo("ExpectedViewName"));     
}

由于MemberShipProvider相当庞大,您可能不会使用其中的所有内容,因此使用@SteveWilkes的方法包装Membership类来创建更小、更有针对性的接口可能是明智的选择。此外,模拟框架可以为您节省大量工作。为了将控制器与新依赖项连接起来,您必须创建一个新的ControllerFactory。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接