仓储模式与单元测试ASP.NET Web API

8

我刚开始接触单元测试,现在才开始理解仓储模式和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.

请注意,我有一个依赖项Roles.IsUserInRole("Admin"),我无法抽象出来,这会导致一些问题。

我的典型的repo接口:

public interface IUserProfileRepository : IDisposable
{
    IEnumerable<UserProfile> Get();
    // Other methods, etc.
}

代码库:

public class UserProfileRepository : IUserProfileRepository, IDisposable
{
    private OfootContext context;

    public UserProfileRepository(OfootContext context)
    {
        this.context = context;
    }

    public IEnumerable<UserProfile> Get()
    {
        return context.UserProfiles.AsEnumerable();
    }

// ... More code

现在一切看起来都很好,我已经将业务访问层从业务逻辑中抽象出来,现在我可以创建一个虚假的存储库来运行单元测试。

虚假存储库:

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

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

    public IEnumerable<UserProfile> Get()
    {
        return 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);
}

现在我们已经在同一页面上了(随时指出任何“代码气味”),以下是我的想法:
  1. 假存储库要求我重新实现所有的Entity Framework逻辑,并将其更改为处理List对象。这需要更多的工作和更多的调试链接。
  2. 如果单元测试通过,它仍然无法说明我访问EF的代码的任何内容,因此我的应用程序仍有可能失败。这意味着我需要单独测试我的EF代码并使其连接到数据库。
  3. 从第1点可以看出,如果单元测试没有测试EF代码,则它只处理我的控制器中的身份验证、授权和用户创建代码。但它无法这样做,因为WebSecurity和Roles类会访问我的数据库。
  4. 如果我将使用数据库来测试EF代码(参见点#2),并且需要一个数据库来测试控制器代码(用于身份验证和授权,参见点#3),那么使用存储库进行抽象化还有什么意义呢?为什么不直接使用IContext之类的方式抽象化上下文,并连接一个使用DropCreateDatabaseAlways类填充的测试数据库呢?

如果我确实找到了一种将用户帐户垃圾邮件抽象化的方法,那么我仍然只是在移动代码和创建更多的代码(甚至可能是两倍?因为我需要创建假文件),而我可以直接替换Context。

我的问题是:我错过了什么?它是一个整体概念还是某个特定的东西?

1个回答

13

你走在正确的道路上。启动项目总是很痛苦的,但是你会发现这将会在未来得到回报。

我建议使用类似于Moq这样的框架,而不是创建“虚假”的对象。它允许您在测试时设置所需的行为,而不必重新实现整个接口。例如,在您的测试中,您可以简单地编写:

    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...

所有这些努力的最终结果是,您完全将测试隔离到了您的控制器中,没有任何数据库依赖性,并且您可以轻松地通过调整模拟设置而不是编写类来改变输入。

如果您遵循这个简单的架构模式,您将获得惊人的可测试性和清晰的职责分离。随着系统变得更加复杂,您可以利用诸如Unity之类的DI容器。

关于身份验证部分,我建议创建属性,您可以像ASP.Net MVC一样对您的方法进行修饰:[Authorization(Roles="Admin")] 作为一个例子。这将创建另一个有用的横切模式,将Auth相关内容与控制器中的业务逻辑解耦。


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