简体   繁体   English

使用Entity Framework单元测试Web API

[英]Unit testing web api using Entity Framework

Any better examples or tutorials available on Unit testing projects using Entity framework than this 使用实体框架的单元测试项目上提供的任何更好的示例或教程都比这更好

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

In my case API project is using Entity framework file Edmx file and accessing the tables from the edmx file from Repository class. 在我的情况下,API项目正在使用实体框架文件Edmx文件,并从Repository类的edmx文件访问表。 [ Not really like the codefirst or dbfirst approach ] [不太喜欢codefirst或dbfirst方法]

The structre of repo class looks like 回购类的结构看起来像

 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;
    }
}

How can i write a proper Unit testing against this way of code architecture 我如何针对这种代码架构编写适当的单元测试

The quick answer is: You don't. 快速的答案是:您没有。 Now, I say this because I tend to regard "Unit Tests" as something that is quick and can be used in continuous integration, while "Integration tests" are the slow tests that only run at night and, of course, when you're working with them. 现在,我之所以这样说,是因为我倾向于将“单元测试”视为快速的东西,可以将其用于连续集成,而“集成测试”是仅在夜间(当然,当您在与他们合作。

The problem you're creating here is that you're using untestable code. 您在此处创建的问题是您正在使用不可测试的代码。

Take your method, "GetStudents()" as an example. 以您的方法“ GetStudents()”为例。 You're depending on the repo to actually exist before calling this method. 在调用此方法之前,您取决于实际存在的存储库。 Any unit-test will depend on Entity Framework being installed, AND of course, this will be super-slow. 任何单元测试都将取决于所安装的Entity Framework,并且当然,这将非常慢。 Imagine a few hundred of these, and your unit test framework is now a serious clog in your system that makes people say "IT's so slow that we don't use it" 想象其中的数百个,您的单元测试框架现在已成为系统中的严重阻塞,使人们说“ IT太慢了,我们不使用它”

A better approach would be to implement the Dependency Inversion Principle 更好的方法是实施依赖倒置原则

First, define an interface: 首先,定义一个接口:

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

Now, your class is just an implementation detail of that contract, for example: 现在,您的类只是该合同的实现细节,例如:

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

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

In the class that uses your repository, you can now inject your instance by constructor injection, and end up with something that is fully unit-testable: 在使用您的存储库的类中,您现在可以通过构造函数注入来注入您的实例,并最终得到完全可单元测试的内容:

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);
    }
}

And now, as the added bonus, you can Unit-Test every last bit of logic, for example: 现在,作为额外的好处,您可以对逻辑的每最后一点进行单元测试,例如:

[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();
}

I'm of the opinon that all your code should be tested. 我认为应该对所有代码进行测试。 This way you can easily detect when some developer breaks an expected chain. 这样,您可以轻松地检测到某些开发人员何时中断了预期的链。 Because of that I always add tests for both repositories and controllers. 因此,我总是为存储库和控制器添加测试。 In your case I would add a test that ensures that your controller uses your repository in a correct way, and that your repository uses EF the right way. 在您的情况下,我将添加一个测试,以确保您的控制器以正确的方式使用存储库,并确保存储库以正确的方式使用EF。 However, you should not test EF itself . 但是, 您不应该测试EF本身 That's Microsofts problem. 那是微软的问题。

First you must abstract the DbContext. 首先,您必须抽象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",
            }
        };

    }

}

Now you have a DbContext that can be tested. 现在,您可以测试一个DbContext。 More information regarding the mocking of DbContext can be found on this blog. 有关模拟DbContext的更多信息,请参见此博客。

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

Then make sure that you can test your repository. 然后确保您可以测试您的存储库。

public class AppBackendRepository
{
    private IDbContext _dbContext;

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

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

It can also be done with a factory. 也可以在工厂完成。

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;
    }
}

Now you can test the repository and make sure that it's using EF the right way. 现在,您可以测试存储库,并确保以正确的方式使用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);
}

What you actually tested is that no developer broke the intended code with something like: 您实际测试的是,没有开发人员会破坏诸如以下内容的预期代码:

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();
    }
}

Now that you know that the repo is doing what it's supposed to, you can simply make sure that your controller is calling the repo and actually returns the value. 既然您知道该存储库正在执行预期的操作,那么您只需确保您的控制器正在调用该存储库并实际返回该值即可。

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

What you actually tested here is that your controller doesn't manipulate the list before returning it, and that it's actually using the repo as intended. 您在此处实际测试的是,控制器不会在返回列表之前对其进行操作,并且实际上已按预期使用了存储库。

Also, you might consider using an IoC like Structuremap or Unity . 另外,您可能会考虑使用类似StructuremapUnityIoC It makes it much easier to make testable applications. 这使得制作可测试的应用程序变得容易得多。

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

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