[英]How to mock and unit test Stored Procedures in EF
我正在使用通用存儲庫模式Repository<TEntity>
,其中存儲庫通過上下文訪問實體。 然后我有一個服務層接受構造函數中的上下文。 現在,我可以在服務中擁有多個存儲庫,通過相同的上下文訪問實體。 很標准。 這非常適合映射到實體的表/視圖,但我無法對通過存儲過程傳遞的數據進行單元測試。
這是我目前的設置:
IDbContext:
public interface IDbContext : IDisposable
{
IDbSet<T> Set<T>() where T : class;
DbEntityEntry<T> Entry<T>(T entity) where T : class;
void SetModified(object entity);
int SaveChanges();
// Added to be able to execute stored procedures
System.Data.Entity.Database Database { get; }
}
語境:
public class AppDataContext : DbContext, IDbContext
{
public AppDataContext()
: base("Name=CONNECTIONSTRING")
{
base.Configuration.ProxyCreationEnabled = false;
}
public new IDbSet<T> Set<T>() where T : class
{
return base.Set<T>();
}
public void SetModified(object entity)
{
Entry(entity).State = EntityState.Modified;
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new BookingMap());
}
// Added to be able to execute stored procedures
System.Data.Entity.Database Database { get { return base.Database; } }
}
通用存儲庫:
public class Repository<T> : IRepository<T> where T : class
{
private readonly IDbContext context;
public Repository(IDbContext context)
{
this.context = context;
}
public IQueryable<T> GetAll()
{
return this.context.Set<T>().AsQueryable();
}
public void Add(T entity)
{
this.context.Set<T>().Add(entity);
}
public void Delete(T entity)
{
this.context.Set<T>().Remove(entity);
}
public void DeleteAll(IEnumerable<T> entities)
{
foreach (var e in entities.ToList())
{
this.context.Set<T>().Remove(e);
}
}
public void Update(T entity)
{
this.context.Set<T>().Attach(entity);
this.context.SetModified(entity);
}
public void SaveChanges()
{
this.context.SaveChanges();
}
public void Dispose()
{
if (this.context != null)
{
this.context.Dispose();
}
}
}
服務:
public class BookingService
{
IDbContext _context;
IRepository<Booking> _bookingRepository;
public BookingService(IDbContext context)
{
_context = context;
_bookingRepository = new Repository<Booking>(context);
}
public IEnumerable<Booking> GetAllBookingsForName(string name)
{
return (from b in _bookingRepository.GetAll()
where b.Name == name
select b);
}
}
測試:
[TestClass]
public class BookingServiceTest
{
[TestMethod]
public void Test_Get_All_Bookings_For_Name()
{
var mock = new Mock<IDbContext>();
mock.Setup(x => x.Set<Booking>())
.Returns(new FakeDbSet<Booking>
{
new Booking { Name = "Foo" },
new Booking { Name = "Bar" }
});
BookingService _bookingService = new BookingService(mock.Object);
var bookings = _bookingService.GetAllBookingsForName(name);
Assert.AreEqual(2, bookings.Count(), "Booking count is not correct");
}
}
這非常適合映射到實體的表/視圖,但我無法對通過存儲過程傳遞的數據進行單元測試。
我在互聯網上查找並找到了DbContext.Database
屬性,我能夠使用.SqlQuery()
函數執行存儲過程並將它們映射到實體類型。
這是我添加到Repository<T>
類的內容:
public IEnumerable<T> SqlQuery(string storedProc, params object[] paramList)
{
return this.context.Database.SqlQuery<T>(storedProc, paramList);
}
並在我的服務類中調用.SqlQuery()
函數:
public IEnumerable<Booking> GetAllBookings(string name)
{
return _bookingRepository.SqlQuery("EXEC GetAllBookings @name = {0}", name);
}
這很好用(我能得到一些數據),但我的問題是如何模擬和單元測試呢?
我剛剛遇到了這樣做的需要,我的谷歌搜索讓我想到了這個問題。 我不喜歡Sriram Sakthivel的答案,當我已經有一個就位時,我不想引入另一個抽象:
我已經有了一個我從DbContext
提取的接口,並在一個test double中實現 。
我只是將int ExecuteSqlCommand(string sql, params object[] parameters)
到我的界面,在實際的上下文中我實現了它:
public int ExecuteSqlCommand(string sql, params object[] parameters)
{
return Database.ExecuteSqlCommand(sql, parameters);
}
這顯然只是委托實際的EF Database
屬性來完成工作。
在我的測試中,我實現了這樣:
public int ExecuteSqlCommand(string sql, params object[] parameters)
{
return 0;
}
哪個沒有真正做任何事情,這就是重點:你不是單元測試實際的存儲過程,你只需要一種方法讓它返回一些有用的東西。
我想在某些時候我可能需要它在單元測試中返回0以外的東西,此時我可能會引入類似Func<int> executeSqlCommandResultFactory
來測試雙構造函數以便我可以控制它,但是目前YAGNI適用。
你可以抽象出Database
的一些接口財產說IDatabase
與SqlQuery
方法。
interface IDatabase
{
public IEnumerable<T> SqlQuery<T>(string sql, params Object[] parameters);
}
class DatabaseWrapper : IDatabase
{
private readonly Database database;
public DatabaseWrapper(Database database)
{
this.database = database;
}
public IEnumerable<T> SqlQuery<T>(string sql, params Object[] parameters)
{
return database.SqlQuery<T>(storedProc, paramList);
}
}
修改您的IDbContext
接口使用IDatabase
,而不是具體的實例,使我們可以嘲笑它。
public interface IDbContext : IDisposable
{
...
// Added to be able to execute stored procedures
IDatabase Database { get; }
}
並以這種方式實現
public class AppDataContext : DbContext, IDbContext
{
private readonly IDatabase database;
public AppDataContext()
: base("Name=CONNECTIONSTRING")
{
base.Configuration.ProxyCreationEnabled = false;
this.database = new DatabaseWrapper(base.Database);
}
...
// Added to be able to execute stored procedures
IDatabase Database { get { return database; } }
}
此時我相信您知道如何模擬IDatabase
以返回測試數據。
我意識到這是一個古老的問題,但對於任何有類似問題的人來說,這是我的看法。
為什么不使用AutoFixture來創建通常從存儲過程返回的數據對象並模擬存儲庫以返回它?
public class FooBar
{
private Fixture fixture;
private Mock<BookingRepository> bookingRepository;
public FooBar()
{
fixture = new Fixture();
bookingRepository= new Mock<BookingRepository>();
}
public void TestInitialize()
{
var sprocObject = fixture.Create<DbObject>();
bookingRepository.Setup(x => x.GetAllBookings(It.IsAny<string>())).Returns(sprocObject);
}
// Unit tests
}
正如Gert Arnold所說,如果要測試實際的存儲過程,則應該進行集成測試。 如果您正在測試服務/存儲庫邏輯,則只需返回一些數據。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.