我通过了所有测试——包括 LinqToObject“模拟”存储库和 LinqToSql“生产”存储库(后者是自动集成测试)。就是这样:
从这篇文章的答案:LINQ-to-SQL : Convert Func<T, T, bool> to an Expression<Func<T, T, bool>>,获取代码以创建PartialApplier
静态类,其作用是重写动态表达(根据我的掌握)。
然后我制作了两者ListRepository
并DataRepository
从RepositoryBase
which implements派生IRepository<T>
。这是回购类:
public abstract class RepositoryBase<T> : IRepository<T>
where T : class
{
public abstract IQueryable<T> Select();
public abstract void Save(T item);
public abstract void Delete(T item);
public abstract void Delete(IEnumerable<T> items);
public virtual Expression<Func<T, T, bool>> KeyCompare { get; private set; }
public virtual int Count { get { return Select().Count(); } }
protected RepositoryBase(Expression<Func<T, T, bool>> keyCompare)
{
KeyCompare = keyCompare;
}
public virtual T Item(T item)
{
var applied = PartialApplier.PartialApply(KeyCompare, item);
var result = (Select().SingleOrDefault(applied));
return result;
}
/// <summary>
/// A method that updates the non-key fields of an existing entity with those of specified <see cref="item"/>.
/// Called by the <see cref="Save"/> method.
/// </summary>
/// <param name="existing">The existing record to update.</param>
/// <param name="item">A <see cref="T"/> object containing the values to update <see cref="existing"/> object with.</param>
/// <returns></returns>
protected abstract void UpdateExisting(T existing, T item);
/// <summary>
/// A method that saves all specified items.
/// </summary>
/// <param name="items">The entities to be saved.</param>
public virtual void Save(IEnumerable<T> items)
{
foreach (var item in items)
{
Save(item);
}
}
}
没有问题,ListRepository
但需要一些返工才能正确派生RepositoryBase
:
/// <summary>
/// A non-persistent repository implementation for mocking the repository layer.
/// </summary>
/// <typeparam name="T"></typeparam>
public class ListRepository<T> : RepositoryBase<T>
where T : class
{
private readonly List<T> _items = new List<T>();
public ListRepository(IEnumerable<T> items, Expression<Func<T, T, bool>> keyCompare)
: base(keyCompare)
{
_items.AddRange(items);
}
public override IQueryable<T> Select()
{
return _items.AsQueryable();
}
public override void Save(T item)
{
var existing = Item(item);
if (existing != null)
{
_items.Remove(item);
}
_items.Add(item);
}
public override void Save(IEnumerable<T> items)
{
foreach (var item in items)
{
Save(item);
}
}
public override void Delete(T item)
{
_items.Remove(item);
}
public override void Delete(IEnumerable<T> items)
{
foreach (var item in items)
{
Delete(Item(item));
}
}
/// <summary>
/// Not implemented.
/// </summary>
/// <param name="existing"></param>
/// <param name="item"></param>
protected override void UpdateExisting(T existing, T item)
{
throw new NotImplementedException(); // list repo just wipes existing data
}
}
然后DataRepository
可能看起来像这样:
public abstract class DataRepository<T> : RepositoryBase<T>
where T : class
{
private DataContext _context;
public DataRepository(DataContext context, Expression<Func<T, T, bool>> keyCompare)
: base(keyCompare)
{
_context = context;
}
public override IQueryable<T> Select()
{
return _context.GetTable<T>().AsQueryable();
}
/// <summary>
/// A method that updates an existing item or creates a new one, as needed.
/// </summary>
/// <param name="item">The entity containing the values to be saved.</param>
public override void Save(T item)
{
var existing = Item(item);
if (existing != null)
{
UpdateExisting(existing, item);
}
else
{
_context.GetTable<T>().InsertOnSubmit(item);
}
_context.SubmitChanges();
}
/// <summary>
/// A method that deletes specified item.
/// </summary>
/// <param name="item"></param>
public override void Delete(T item)
{
var existing = Item(item);
if (existing != null)
{
_context.GetTable<T>().DeleteOnSubmit(existing);
}
_context.SubmitChanges();
}
public override void Delete(IEnumerable<T> items)
{
foreach (var item in items)
{
Delete(item);
}
}
protected override void UpdateExisting(T existing, T item)
{
throw new NotImplementedException(); // must override in derived classes - not obvious I know
}
}
因此,给定一个只有一个简单对象(DataRepositoryIntegrationTest
在本例中称为)的 DataContext,我们可以创建一个实现来测试它:
public class TestRepository : DataRepository<DataRepositoryIntegrationTest>
{
public TestRepository(DataContext context)
: base(context, (item1, item2) => item1.Id == item2.Id)
{ }
protected override void UpdateExisting(DataRepositoryIntegrationTest existing, DataRepositoryIntegrationTest item)
{
existing.Value = item.Value;
existing.DateUpdated = DateTime.Now;
}
}
然后可以编写测试:
[TestClass]
public class DataRepositoryTests
{
private IRepository<DataRepositoryIntegrationTest> _repo;
private static DataContext _context;
[ClassCleanup]
public static void CleanUp()
{
_context.Dispose();
}
public DataRepositoryTests()
{
_context = new DataContext(Settings.Default.IntegrationTestConnectionString);
}
[TestInitialize]
public void InitRepository()
{
_context.ExecuteCommand("truncate table dbo.IntegrationTest");
_context.ExecuteCommand("insert into dbo.IntegrationTest ([Value], [DateInserted]) values ('Test1', getdate())");
_context.ExecuteCommand("insert into dbo.IntegrationTest ([Value], [DateInserted]) values ('Test2', getdate())");
_context.ExecuteCommand("insert into dbo.IntegrationTest ([Value], [DateInserted]) values ('Test3', getdate())");
_repo = new TestRepository(_context);
}
private DataRepositoryIntegrationTest CreateItem(string value)
{
return new DataRepositoryIntegrationTest { Value = value, DateInserted = DateTime.Now };
}
[TestMethod]
public void ShouldInsertItem()
{
// arrange
var item = CreateItem("Test.ShouldInsert1");
var expectedCount = _repo.Count + 1;
// act
_repo.Save(item);
var actualCount = _repo.Count;
// assert
Assert.AreEqual(expectedCount, actualCount);
}
[TestMethod]
public void ShouldInsertItems()
{
// arrange
var count = _repo.Count;
var items = new DataRepositoryIntegrationTest[] {
CreateItem("Test.ShouldInsert1"),
CreateItem("Test.ShouldInsert2")};
var expected = count + items.Length;
// act
_repo.Save(items);
var actual = _repo.Count;
// assert
Assert.AreEqual(expected, actual);
}
[TestMethod]
public void ShouldUpdateItem()
{
if (_repo.Count == 0) throw new AssertInconclusiveException("ShouldUpdateItem requires an existing item.");
// arrange
var item = _repo.Item(new DataRepositoryIntegrationTest { Id = 1 });
var expected = "updated";
item.Value = expected;
// act
_repo.Save(item);
var result = _repo.Item(new DataRepositoryIntegrationTest { Id = item.Id });
var actual = result.Value;
// assert
Assert.AreEqual(expected, actual);
}
[TestMethod]
public void ShouldDeleteItem()
{
if (_repo.Count == 0) throw new AssertInconclusiveException("ShouldUpdateItem requires an existing item.");
// arrange
var item = _repo.Item(new DataRepositoryIntegrationTest { Id = 1 });
var expected = _repo.Count - 1;
// act
_repo.Delete(item);
var actual = _repo.Count;
// assert
Assert.AreEqual(expected, actual);
}
[TestMethod]
public void ShouldDeleteItems()
{
if (_repo.Count < 2) throw new AssertInconclusiveException("ShouldUpdateItem requires 2 existing items.");
// arrange
var items = new DataRepositoryIntegrationTest[] {
new DataRepositoryIntegrationTest { Id = 1 },
new DataRepositoryIntegrationTest { Id = 2 }};
var expected = _repo.Count - items.Count();
// act
_repo.Delete(items);
var actual = _repo.Count;
// assert
Assert.AreEqual(expected, actual);
}
}
他们都通过了!