Asp.net mvc 存储库模式和单元测试ASP.NET Web API

Asp.net mvc 存储库模式和单元测试ASP.NET Web API,asp.net-mvc,unit-testing,asp.net-web-api,repository-pattern,Asp.net Mvc,Unit Testing,Asp.net Web Api,Repository Pattern,我刚刚开始深入单元测试,并且刚刚开始掌握存储库模式和IoC。然而,我不认为我完全理解它,因为它的某些部分似乎有点愚蠢。让我解释一下 我的控制器: public class UserProfileController : ApiController { private IUserProfileRepository repository; // Optional constructor, passes repository, allows dependency injection

我刚刚开始深入单元测试,并且刚刚开始掌握存储库模式和IoC。然而,我不认为我完全理解它,因为它的某些部分似乎有点愚蠢。让我解释一下

我的控制器:

public class UserProfileController : ApiController
{
    private IUserProfileRepository repository;

    // Optional constructor, passes repository, allows dependency injection
    public UserProfileController(IUserProfileRepository userProfileRepository)
    {
        this.repository = userProfileRepository;
    }

    // GET api/UserProfile
    // Returns a list of all users
    public IEnumerable<UserProfile> Get()
    {
        // Only Admins can see a list of users
        if (Roles.IsUserInRole("Admin"))
        {
            return repository.Get();
        }
        else
        {
            throw new HttpResponseException(
                new HttpResponseMessage(HttpStatusCode.Forbidden)
                {
                    ReasonPhrase = "Administrator access required"
                });
        }
    }

// Other methods, etc.
公共类UserProfileController:ApicController
{
私有IUSERPROFILE存储库;
//可选构造函数,传递存储库,允许依赖项注入
公共UserProfileController(IUserProfileRepository userProfileRepository)
{
this.repository=userProfileRepository;
}
//获取api/UserProfile
//返回所有用户的列表
公共IEnumerable Get()
{
//只有管理员才能看到用户列表
if(Roles.IsUserInRole(“Admin”))
{
返回repository.Get();
}
其他的
{
抛出新的HttpResponseException(
新的HttpResponseMessage(HttpStatusCode.Forbidden)
{
ReasonPhrase=“需要管理员访问”
});
}
}
//其他方法等。
(注意,我有一个依赖项Roles.IsUserInRole(“Admin”),我不知道如何进行抽象,这会导致一些问题)

我的典型回购界面:

public interface IUserProfileRepository : IDisposable
{
    IEnumerable<UserProfile> Get();
    // Other methods, etc.
}
公共接口IUserProfileRepository:IDisposable { IEnumerable Get(); //其他方法等。 } 回购:

公共类UserProfileRepository:IUserProfileRepository,IDisposable
{
语境的私密性;
公共UserProfileRepository(OfootContext上下文)
{
this.context=上下文;
}
公共IEnumerable Get()
{
返回context.UserProfiles.AsEnumerable();
}
//…更多代码
所以一切看起来都很好,我已经从我的业务逻辑中抽象出了我的业务访问层,现在我可以创建一个假的存储库来运行单元测试

虚假回购:

public class FakeUserProfileRepository : IUserProfileRepository, IDisposable
{
    private List<UserProfile> context;

    public FakeUserProfileRepository(List<UserProfile> context)
    {
        this.context = context;
    }

    public IEnumerable<UserProfile> Get()
    {
        return context.AsEnumerable();
    }
公共类FakeUserProfileRepository:IUserProfileRepository,IDisposable
{
私有列表上下文;
公共FakeUserProfileRepository(列表上下文)
{
this.context=上下文;
}
公共IEnumerable Get()
{
返回context.AsEnumerable();
}
和测试:

[TestMethod]
public void GetUsers()
{
    // Arrange
    var items = new List<UserProfile>()
    {
        new UserProfile
        {
            UserId = 1,
            Username = "Bob",
        },
        new UserProfile
        {
            UserId = 2,
            Username = "Bob2",
        }
    };

    FakeUserProfileRepository repo = new FakeUserProfileRepository(
        items);
    UserProfileController controller = new UserProfileController(
        repo);

    // Act
    IEnumerable<UserProfile> result = controller.Get();

    // Assert
    Assert.IsNotNull(result);
}
[TestMethod]
public void GetUsers()
{
//安排
var items=新列表()
{
新用户配置文件
{
UserId=1,
Username=“Bob”,
},
新用户配置文件
{
UserId=2,
Username=“Bob2”,
}
};
FakeUserProfileRepository repo=新建FakeUserProfileRepository(
项目);
UserProfileController=新的UserProfileController(
回购协议);
//表演
IEnumerable result=controller.Get();
//断言
Assert.IsNotNull(结果);
}
现在我们已经在同一页上了(并且可以随意指出任何“代码气味”),下面是我的想法:

  • 伪存储库要求我重新实现所有实体框架逻辑,并将其更改为处理列表对象。这是我需要调试的更多工作和链中的更多链接
  • 如果单元测试通过,它不会说明我访问EF的代码,因此我的应用程序仍然可能失败。这只是意味着我需要单独测试我的EF代码,并让它访问数据库
  • 从#1开始,如果单元测试没有测试EF代码,那么它只是处理我控制器中的身份验证、授权和用户创建代码。它不能,因为WebSecurity和Roles类命中了我的数据库
  • 如果我要使用数据库来测试EF代码(第2点),并且需要一个数据库来测试控制器代码(用于身份验证和授权,第3点),那么为什么还要用存储库进行抽象呢?为什么我不直接抽象上下文(使用IContext或其他什么?)并连接使用DropCreateDatabaseAlways类填充的测试数据库
  • 如果我真的找到了一种方法来提取用户帐户垃圾,那么我仍然只是在代码周围游荡,并创建更多的代码(可能甚至是两倍,因为我需要创建赝品),这样我就可以替换上下文


    我的问题是:我遗漏了什么?这是一个整体概念还是一个具体的概念?

    你走在正确的轨道上。启动和运行东西总是很痛苦的,但你会发现这会带来回报

    与其创建“伪”对象,不如推荐一个类似的框架。它允许您在测试时设置所需的行为,而不是重新实现整个接口。例如,在测试中,您可以简单地编写:

        Mock<IUserProfileRepository> mockUserRepo = new Mock<IUserProfileRepository>();
        var items = new List<UserProfile>()
        {
            new UserProfile
            {
                UserId = 1,
                Username = "Bob",
            },
            new UserProfile
            {
                UserId = 2,
                Username = "Bob2",
            }
        };
       mockUserRepo.Setup(m => m.Get().Returns(items.AsEnumerable());
       UserProfileController controller = new UserProfileController(
            mockUserRepo.Object);
    
        // Act
       IEnumerable<UserProfile> result = controller.Get();
       //Now you can keep varying the mock response by changing the Setup(), so now 
       //check for null response handling, 0 items, exceptions etc...
    
    Mock mockUserRepo=new Mock();
    var items=新列表()
    {
    新用户配置文件
    {
    UserId=1,
    Username=“Bob”,
    },
    新用户配置文件
    {
    UserId=2,
    Username=“Bob2”,
    }
    };
    mockUserRepo.Setup(m=>m.Get().Returns(items.AsEnumerable());
    UserProfileController=新的UserProfileController(
    mockUserRepo.Object);
    //表演
    IEnumerable result=controller.Get();
    //现在,您可以通过更改Setup()来不断更改模拟响应,所以现在
    //检查空响应处理、0项、异常等。。。
    
    所有这些工作的最终结果是,您已经将测试完全隔离到您的控制器,没有DB依赖关系,您可以轻松地更改输入,而无需编写类,而是使用模拟设置

    如果您遵循这个简单的体系结构模式,您将获得极好的可测试性和清晰的关注点分离。随着系统中的事情变得更加复杂,您可以利用Unity这样的DI容器

    在身份验证部分,我建议您创建一些属性来装饰您的方法,例如ASP.Net MVC使用:[Authorization(Roles=“Admin”)]作为示例。这将创建另一个有用的横切模式
        Mock<IUserProfileRepository> mockUserRepo = new Mock<IUserProfileRepository>();
        var items = new List<UserProfile>()
        {
            new UserProfile
            {
                UserId = 1,
                Username = "Bob",
            },
            new UserProfile
            {
                UserId = 2,
                Username = "Bob2",
            }
        };
       mockUserRepo.Setup(m => m.Get().Returns(items.AsEnumerable());
       UserProfileController controller = new UserProfileController(
            mockUserRepo.Object);
    
        // Act
       IEnumerable<UserProfile> result = controller.Get();
       //Now you can keep varying the mock response by changing the Setup(), so now 
       //check for null response handling, 0 items, exceptions etc...