[英]Unit testing web api using Entity Framework
使用實體框架的單元測試項目上提供的任何更好的示例或教程都比這更好
在我的情況下,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.
}
您在此處實際測試的是,控制器不會在返回列表之前對其進行操作,並且實際上已按預期使用了存儲庫。
另外,您可能會考慮使用類似Structuremap或Unity的IoC 。 這使得制作可測試的應用程序變得容易得多。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.