单元测试:如何测试通用存储库
Unit testing: How to test generic repository
我正在尝试测试我的通用存储库,它看起来像这样
public class GenericRepository<T> : IGenericRepository<T> where T : BaseEntity
{
protected readonly DbContext DbContext;
public GenericRepository(DbContext dbContext)
{
DbContext = dbContext;
}
public string Create(T item)
{
if (string.IsNullOrEmpty(item.Id))
{
item.Id = Guid.NewGuid().ToString("N");
}
item.CreatedAt = DateTime.UtcNow;
DbContext.Entry(item).State = EntityState.Added;
DbContext.SaveChanges();
return item.Id;
/*using (var dbContext = new MyDbContext())
{
item.Id = Guid.NewGuid().ToString("N");
item.CreatedAt = DateTime.UtcNow;
dbContext.Entry(item).State = EntityState.Added;
dbContext.SaveChanges();
return item.Id;
}*/
}
public T GetById(string id)
{
return GetFirst(x => x.Id == id);
}
public T GetFirst(Expression<Func<T, bool>> @where, params Expression<Func<T, object>>[] nav)
{
return GetFiltered(nav).FirstOrDefault(where);
/*using (var context = new MyDbContext())
{
return GetFiltered(context, nav).FirstOrDefault(where);
}*/
}
private IQueryable<T> GetFiltered(params Expression<Func<T, object>>[] nav)
{
IQueryable<T> q = DbContext.Set<T>();
return nav.Aggregate(q, (current, n) => current.Include(n));
}
}
基于microft's testing fundamental site我试着写了几个测试用例。
下面是单元测试代码
[TestClass]
public class GenericRepositoryTest
{
private Foo _foo;
private IQueryable<Foo> _fooList;
private Mock<DbSet<Foo>> _mockSet;
[TestInitialize]
public void Setup()
{
_foo = new Foo
{
EmailId = "foo@bar.com",
FirstName = "foo",
LastName = "bar",
ProfileId = 27,
IsDeleted = false,
};
_fooList = new List<Foo>
{
new Foo{EmailId = "one@bar.com", FirstName = "one", LastName = "bar", ProfileId = 28, IsDeleted = false},
new Foo{EmailId = "two@bar.com", FirstName = "two", LastName = "bar", ProfileId = 29, IsDeleted = false},
}.AsQueryable();
_mockSet = new Mock<DbSet<Foo>>();
_mockSet.As<IQueryable<Foo>>().Setup(m => m.Provider).Returns(_fooList.Provider);
_mockSet.As<IQueryable<Foo>>().Setup(m => m.Expression).Returns(_fooList.Expression);
_mockSet.As<IQueryable<Foo>>().Setup(m => m.ElementType).Returns(_fooList.ElementType);
_mockSet.As<IQueryable<Foo>>().Setup(m=>m.GetEnumerator()).Returns(_fooList.GetEnumerator());
}
[TestMethod]
public void Create_GivenEntity_ReturnsGuidId()
{
//Arrange
var guidId = Guid.NewGuid().ToString("N");
var dbContext = new Mock<MyDbContext>().Object;
IGenericRepository<Foo> genericRepository = new Mock<GenericRepository<Foo>>(dbContext).Object;
//Act
_waitingQueue.Id = guidId;
var actualId = genericRepository.Create(_foo);
//Assert
Assert.IsNotNull(actualId);
Assert.AreEqual(actualId, guidId);
}
[TestMethod]
public void GetById_GivenEntityId_ReturnsEntity()
{
//Arrange
var id = Guid.NewGuid().ToString("N");
_foo.Id = id;
var mockContext = new Mock<MyDbContext>();
mockContext.Setup(c => c.Foo).Returns(_mockSet.Object);
IGenericRepository<Foo> genericRepository = new Mock<GenericRepository<Foo>>(mockContext.Object).Object;
//Act
var fooId = genericRepository.Create(_foo);
var fooObject = genericRepository.GetById(id);
//Assert
fooObject.PropertiesShouldEqual(_foo);
}
}
这里是 DbContext
public class MyDbContext : DbContext
{
public MyDbContext() : base("fakeConnectionString")
{
}
public virtual DbSet<Foo> WaitingQueues { get; set; }
}
我是单元测试的新手,我不知道我采用的方法是否正确。
目前,第一个测试 Create_GivenEntity_ReturnsGuidId
通过,但第二个测试 GetById_GivenEntityId_ReturnsEntity
失败。
我得到的错误是在 GenericRepository
的 GetFirst(Expression<Func<T, bool>> @where, params Expression<Func<T, object>>[] nav)
方法上
这是因为 GetFiltered(params Expression<Func<T, object>>[] nav)
被 GetFirst
returns 调用时为空。
是否因为此时 T
对 GenericRepository<T>
未知?
我得到的错误是
System.ArgumentNullException
HResult=0x80004003
Message=Value cannot be null.
Parameter name: source
当我调试时,我可以看到 GetById(string id)
方法已将正确的 ID 传递给它
谁能建议我该怎么做?我应该采取什么方法?
您没有设置 DbContext.Set(),因此,由于 Mock 默认处于松散行为状态,因此它应该 returns null。
集的类型不正确。
// should be Mock<DbSet<Foo>>
private Mock<DbSet<WaitingQueue>> _mockSet;
// what it MyDbContext ? should not it be DbContext ? has it is in
// GenericRepository
var mockContext = new Mock<MyDbContext>();
// what is the point of this line ?
mockContext.Setup(c => c.WaitingQueues).Returns(_mockSet.Object);
// how to setup
mockContext.Setup(c => c.Set<Foo>()).Returns(_mockSet.Object);
nav 始终为空,因为您仅使用谓词调用 GetFirst。
public T GetById(string id)
{
// nav is null
return GetFirst(x => x.Id == id);
}
此外,如果您想测试 class GenericRepository,请不要在测试中模拟它,否则对其进行单元测试有什么意义?
你要测试的是背后的逻辑,测试它是否可以处理所有输入。
例如,对于方法GetById,当id为null时进行测试,id为空,id不引用现有实体且测试成功(找到实体)。
我正在尝试测试我的通用存储库,它看起来像这样
public class GenericRepository<T> : IGenericRepository<T> where T : BaseEntity
{
protected readonly DbContext DbContext;
public GenericRepository(DbContext dbContext)
{
DbContext = dbContext;
}
public string Create(T item)
{
if (string.IsNullOrEmpty(item.Id))
{
item.Id = Guid.NewGuid().ToString("N");
}
item.CreatedAt = DateTime.UtcNow;
DbContext.Entry(item).State = EntityState.Added;
DbContext.SaveChanges();
return item.Id;
/*using (var dbContext = new MyDbContext())
{
item.Id = Guid.NewGuid().ToString("N");
item.CreatedAt = DateTime.UtcNow;
dbContext.Entry(item).State = EntityState.Added;
dbContext.SaveChanges();
return item.Id;
}*/
}
public T GetById(string id)
{
return GetFirst(x => x.Id == id);
}
public T GetFirst(Expression<Func<T, bool>> @where, params Expression<Func<T, object>>[] nav)
{
return GetFiltered(nav).FirstOrDefault(where);
/*using (var context = new MyDbContext())
{
return GetFiltered(context, nav).FirstOrDefault(where);
}*/
}
private IQueryable<T> GetFiltered(params Expression<Func<T, object>>[] nav)
{
IQueryable<T> q = DbContext.Set<T>();
return nav.Aggregate(q, (current, n) => current.Include(n));
}
}
基于microft's testing fundamental site我试着写了几个测试用例。
下面是单元测试代码
[TestClass]
public class GenericRepositoryTest
{
private Foo _foo;
private IQueryable<Foo> _fooList;
private Mock<DbSet<Foo>> _mockSet;
[TestInitialize]
public void Setup()
{
_foo = new Foo
{
EmailId = "foo@bar.com",
FirstName = "foo",
LastName = "bar",
ProfileId = 27,
IsDeleted = false,
};
_fooList = new List<Foo>
{
new Foo{EmailId = "one@bar.com", FirstName = "one", LastName = "bar", ProfileId = 28, IsDeleted = false},
new Foo{EmailId = "two@bar.com", FirstName = "two", LastName = "bar", ProfileId = 29, IsDeleted = false},
}.AsQueryable();
_mockSet = new Mock<DbSet<Foo>>();
_mockSet.As<IQueryable<Foo>>().Setup(m => m.Provider).Returns(_fooList.Provider);
_mockSet.As<IQueryable<Foo>>().Setup(m => m.Expression).Returns(_fooList.Expression);
_mockSet.As<IQueryable<Foo>>().Setup(m => m.ElementType).Returns(_fooList.ElementType);
_mockSet.As<IQueryable<Foo>>().Setup(m=>m.GetEnumerator()).Returns(_fooList.GetEnumerator());
}
[TestMethod]
public void Create_GivenEntity_ReturnsGuidId()
{
//Arrange
var guidId = Guid.NewGuid().ToString("N");
var dbContext = new Mock<MyDbContext>().Object;
IGenericRepository<Foo> genericRepository = new Mock<GenericRepository<Foo>>(dbContext).Object;
//Act
_waitingQueue.Id = guidId;
var actualId = genericRepository.Create(_foo);
//Assert
Assert.IsNotNull(actualId);
Assert.AreEqual(actualId, guidId);
}
[TestMethod]
public void GetById_GivenEntityId_ReturnsEntity()
{
//Arrange
var id = Guid.NewGuid().ToString("N");
_foo.Id = id;
var mockContext = new Mock<MyDbContext>();
mockContext.Setup(c => c.Foo).Returns(_mockSet.Object);
IGenericRepository<Foo> genericRepository = new Mock<GenericRepository<Foo>>(mockContext.Object).Object;
//Act
var fooId = genericRepository.Create(_foo);
var fooObject = genericRepository.GetById(id);
//Assert
fooObject.PropertiesShouldEqual(_foo);
}
}
这里是 DbContext
public class MyDbContext : DbContext
{
public MyDbContext() : base("fakeConnectionString")
{
}
public virtual DbSet<Foo> WaitingQueues { get; set; }
}
我是单元测试的新手,我不知道我采用的方法是否正确。
目前,第一个测试 Create_GivenEntity_ReturnsGuidId
通过,但第二个测试 GetById_GivenEntityId_ReturnsEntity
失败。
我得到的错误是在 GenericRepository
的GetFirst(Expression<Func<T, bool>> @where, params Expression<Func<T, object>>[] nav)
方法上
这是因为 GetFiltered(params Expression<Func<T, object>>[] nav)
被 GetFirst
returns 调用时为空。
是否因为此时 T
对 GenericRepository<T>
未知?
我得到的错误是
System.ArgumentNullException
HResult=0x80004003
Message=Value cannot be null.
Parameter name: source
当我调试时,我可以看到 GetById(string id)
方法已将正确的 ID 传递给它
谁能建议我该怎么做?我应该采取什么方法?
您没有设置 DbContext.Set(),因此,由于 Mock 默认处于松散行为状态,因此它应该 returns null。
集的类型不正确。
// should be Mock<DbSet<Foo>>
private Mock<DbSet<WaitingQueue>> _mockSet;
// what it MyDbContext ? should not it be DbContext ? has it is in
// GenericRepository
var mockContext = new Mock<MyDbContext>();
// what is the point of this line ?
mockContext.Setup(c => c.WaitingQueues).Returns(_mockSet.Object);
// how to setup
mockContext.Setup(c => c.Set<Foo>()).Returns(_mockSet.Object);
nav 始终为空,因为您仅使用谓词调用 GetFirst。
public T GetById(string id)
{
// nav is null
return GetFirst(x => x.Id == id);
}
此外,如果您想测试 class GenericRepository,请不要在测试中模拟它,否则对其进行单元测试有什么意义?
你要测试的是背后的逻辑,测试它是否可以处理所有输入。
例如,对于方法GetById,当id为null时进行测试,id为空,id不引用现有实体且测试成功(找到实体)。