繁体   English   中英

使用Entity Framework单元测试Web API

[英]Unit testing web api using Entity Framework

使用实体框架的单元测试项目上提供的任何更好的示例或教程都比这更好

http://www.asp.net/web-api/overview/testing-and-debugging/mocking-entity-framework-when-unit-testing-aspnet-web-api-2

在我的情况下,API项目正在使用实体框架文件Edmx文件,并从Repository类的edmx文件访问表。 [不太喜欢codefirst或dbfirst方法]

回购类的结构看起来像

 public class AppBackendRepository
{
    // modify the type of the db field
    private AppDBEntities db_context = new AppDBEntities();

    public List<Student> Get()
    {
        return db_context.Students.ToList();
    }
}



public class StudentController
{

    private static AppBackendRepository repo;
    public  StudentController()
    {

        repo = new AppBackendRepository();
    }

    public  IEnumerable<Student> GetStudents()
    {
        List<Student> students = repo.Get();

        return students;
    }
}

我如何针对这种代码架构编写适当的单元测试

快速的答案是:您没有。 现在,我之所以这样说,是因为我倾向于将“单元测试”视为快速的东西,可以将其用于连续集成,而“集成测试”是仅在夜间(当然,当您在与他们合作。

您在此处创建的问题是您正在使用不可测试的代码。

以您的方法“ GetStudents()”为例。 在调用此方法之前,您取决于实际存在的存储库。 任何单元测试都将取决于所安装的Entity Framework,并且当然,这将非常慢。 想象其中的数百个,您的单元测试框架现在已成为系统中的严重阻塞,使人们说“ IT太慢了,我们不使用它”

更好的方法是实施依赖倒置原则

首先,定义一个接口:

public interface IStudentRepository
{
    IEnumerable<Student> GetStudents();
}

现在,您的类只是该合同的实现细节,例如:

public class StudentRepository : DbContext, IStudentRepository
{
    private DbSet<Student> Students;

    public IEnumerable<Student> GetStudents()
    {
        return Students;
    }
}

在使用您的存储库的类中,您现在可以通过构造函数注入来注入您的实例,并最终得到完全可单元测试的内容:

public class StudentEnrollment
{
    private readonly IStudentRepository _studentRepository;

    // Inject the contract here
    public StudentEnrollment(IStudentRepository studentRepository)
    {
        _studentRepository = studentRepository;
    }


    public IEnumerable<Student> GetStudentsForClass(StudentClass studentClass)
    {
        return _studentRepository.GetStudents().Where(student => student.class == studentClass);
    }
}

现在,作为额外的好处,您可以对逻辑的每最后一点进行单元测试,例如:

[TestMethod]
public void GetStudentsForClass_GetStudentsThrowsException_ResultIsNull()
{
    // Arrange
    var mock = Mock.Create<IStudentRepository();
    var badException = new Exception("I'm bad");
    mock.Setup(repo => repo.GetStudents()).Throws(badException);
    var someClass = new StudentClass();
    var instance = new StudentEnrollment(mock.object);

    // Act
    var result = instance.GetStudentsForClass(studentClass);

    // Assert
    result.ShouldBeEmpty();
}

我认为应该对所有代码进行测试。 这样,您可以轻松地检测到某些开发人员何时中断了预期的链。 因此,我总是为存储库和控制器添加测试。 在您的情况下,我将添加一个测试,以确保您的控制器以正确的方式使用存储库,并确保存储库以正确的方式使用EF。 但是, 您不应该测试EF本身 那是微软的问题。

首先,您必须抽象DbContext。

public class YourContext : DbContext, IDbContext
{
    public virtual IDbSet<Student> Students { get; set; }
}

public interface IDbContext
{
    IDbSet<Student> Students;
}


// Util for creating a testable context.
public class ContextUtils
{
    internal static IDbSet<T> GetMockDbSet<T>(IEnumerable<T> data) where T : class
    {
        IQueryable<T> queryable = data.AsQueryable();

        IDbSet<T> dbSet = MockRepository.GenerateMock<IDbSet<T>, IQueryable>();

        dbSet.Stub(m => m.Provider).Return(queryable.Provider);
        dbSet.Stub(m => m.Expression).Return(queryable.Expression);
        dbSet.Stub(m => m.ElementType).Return(queryable.ElementType);
        dbSet.Stub(m => m.GetEnumerator()).Return(queryable.GetEnumerator());

        return dbSet;
    }

    public static IDbContext GetMockDbContext()
    {
        var dbContext = MockRepository.GenerateMock<IDbContext>();
        dbContext.Stub(x => x.Student).PropertyBehavior();
        dbContext.Students = GetMockDbSet(GetStudents());
        return dbContext;
    }

    private static IEnumerable<Student> GetStudents()
    {
        // Create some mock data.
        return new List<Student>
        {
            new Student()
            {
                StudentID = 1,
                Name = "Student One",
            },
            new Student()
            {
                StudentID = 2,
                Name = "Student Two",
            },
            new Student()
            {
                StudentID = 3,
                Name = "Student Three",
            }
        };

    }

}

现在,您可以测试一个DbContext。 有关模拟DbContext的更多信息,请参见此博客。

http://aikmeng.com/post/62817541825/how-to-mock-dbcontext-and-dbset-with-moq-for-unit

然后确保您可以测试您的存储库。

public class AppBackendRepository
{
    private IDbContext _dbContext;

    // With injection.
    public AppBackendRepository(IDbContext context)
    {
        _dbContext = context;
    }

    public List<Student> Get()
    {
        return _dbContext.Students.ToList();
    }
}

也可以在工厂完成。

public class AppBackendRepository
{
    public List<Student> Get()
    {
        using (var context = DbContextFactory.GenerateContext())
        {
            return context .Students.ToList();
        }
    }
}

public interface IDbContextFactory
{
    /// <summary>
    /// Creates a new context.
    /// </summary>
    /// <returns></returns>
    IDbContext GenerateContext();

    /// <summary>
    /// Returns the previously created context.
    /// </summary>
    /// <returns></returns>
    IDbContext GetCurrentContext();
}

public class DbContextFactory : IDbContextFactory
{
    private IDbContext _context;

    public IDbContext GenerateContext()
    {
        _context = new DbContext();
        return _context;
    }

    public IDbContext GetCurrentContext()
    {
        if (_context == null)
            _context = GenerateContext();
        return _context;
    }
}

现在,您可以测试存储库,并确保以正确的方式使用EF。

[TestMethod]
public void ShouldReturnAllValues()
{
    int correctAmount = 3; // The number specified in MockUtils.
    var dbContext = MockUtils.GetMockDbSet();
    var repo = new AppBackendRepository(dbContext);
    var result = repo.Get();
    Assert.IsTrue(result.Count() == correctAmount);
}

您实际测试的是,没有开发人员会破坏诸如以下内容的预期代码:

public class AppBackendRepository
{
    private IDbContext _dbContext;

    // With injection.
    public AppBackendRepository(IDbContext context)
    {
        _dbContext = context;
    }

    public List<Student> Get()
    {
        // Only active...
        return _dbContext.Students.Where(x => x.Active).ToList();
    }
}

既然您知道该存储库正在执行预期的操作,那么您只需确保您的控制器正在调用该存储库并实际返回该值即可。

public class StudentController
{
    private static IAppBackendRepository _repo;
    public  StudentController(IAppBackendRepository repo)
    {
        _repo = repo;
    }

    public  IEnumerable<Student> GetStudents()
    {
        List<Student> students = _repo.Get();
        return students;
    }
}

[TestMethod]
public void ShouldCallRepo()
{
    // With Rhino
    var mockRepo = MockRepository.GenerateStub<IAppBackendRepository>();
    var expectedResult = new List<Student>();
    mockRepo.Expect(x => x.Get()).Return(expectedResult);
    var controller = new StudentController(mockRepo);
    var actualResult = controller.GetStudents();
    mockRepo.VerifyAllExpectations();
    Assert.AreEqual(actualResult, expectedResult); // Possible in it's own method.
}

您在此处实际测试的是,控制器不会在返回列表之前对其进行操作,并且实际上已按预期使用了存储库。

另外,您可能会考虑使用类似StructuremapUnityIoC 这使得制作可测试的应用程序变得容易得多。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM