如何在运行时将 属性 附加到最小起订量对象中
How do I append a Property into an MOQ Object at Runtime
我希望在 运行 时动态创建一个 属性,以便安装程序可以将 属性 用于其数据 and/or 数据库值:
问题:
如何动态地将 属性 附加到模拟实例?
示例:
public Mock<IRepository<tClaims>> MockClaimsRepository()
{
var repository = new Mock<IRepository<tClaims>>();
// HOW DO I DO THIS?
repository.SetupProperty<Claim>(//* How do I append a dynamic property here *//)
repository.SetupProperty<List<Claims>>(//* How do I append a dynamic property here *//)
repository.Setup(x => x.GetActive()).Returns(repository.Claims.AsQueryable());
repository.Setup(x => x.GetAll()).Returns(repository.Claims.AsQueryable());
repository.Setup(x => x.GetById(repository.Claim.Claim_ID)).Returns(Claim);
repository.Setup(x => x.Find(new object[] { It.IsAny<object>() })).Returns(repository.Claim);
repository.Setup(x => x.Add(repository.Claim)).Verifiable();
repository.Setup(x => x.AddRange(repository.Claims)).Verifiable();
repository.Setup(x => x.Update(repository.Claim)).Verifiable();
repository.Setup(x => x.Delete(repository.Claim)).Verifiable();
repository.Setup(x => x.Delete(It.IsAny<object>())).Verifiable();
return repository;
}
您不能直接向模拟对象添加新属性,您可以使用具有所需属性的 As<>
方法添加另一个接口的实现。
但是如果您的目标是使用此属性为模拟提供逻辑和状态,那么您可以为此使用变量,如下所示:
public Mock<IRepository<Claims>> MockClaimsRepository()
{
var repository = new Mock<IRepository<Claims>>();
var claims = new List<Claims>(); // Add variables to be used in the setups
repository.Setup(x => x.GetAll()).Returns(claims.AsQueryable());
repository.Setup(x => x.GetById(It.IsAny<int>())).Returns<int>(id => claims.Find(c => c.Id == id));
repository.Setup(x => x.Add(It.IsAny<Claims>())).Callback<Claims>(c => claims.Add(c));
repository.Setup(x => x.Delete(It.IsAny<Claims>())).Callback<Claims>(c => claims.Remove(c));
...
return repository;
}
当您从 MockClaimsRepository
return 时,这些变量将不会被释放,因为您在设置中引用它。
您可以使用 SetupAllProperties() 吗?
Specifies that the all properties on the mock should have "property behavior",
meaning that setting its value will cause it to be saved and
later returned when the property is requested. (this is also
known as "stubbing"). The default value for each property will be the
one generated as specified by the property for the mock.
只是将 Arturo 更新为像您的存储库一样通用:
public Mock<IRepository<T>> MockRepository<T, TKey>() where T : BaseEntity<TKey> //(for Id )
{
var repository = new Mock<IRepository<T>>();
var entities = new List<T>(); // Add variables to be used in the setups
repository.Setup(x => x.GetAll()).Returns(entities.AsQueryable());
repository.Setup(x => x.GetById(It.IsAny<TKey>())).Returns<TKey>(id => entities.Find(c => c.Id == id));
repository.Setup(x => x.Add(It.IsAny<T>())).Callback<T>(c => entities.Add(c));
repository.Setup(x => x.Delete(It.IsAny<T>())).Callback<T>(c => entities.Remove(c));
...
return repository;
}
虽然 "basically" 正确,但我无法将给定答案中的任何一个标记为正确的原因是他们没有提供 "full" 解决问题的方法(例如,为测试引用自定义属性的能力 )。
可能有更好的方法来执行此操作,但这是对我有用的方法...
期望的结果:
- 模拟我的 UnitOfWork(使用最小起订量)
- 模拟我的 UnitOfWork 中的所有存储库(使用 Moq)
- 在模拟(如数据库)中使用来自属性的虚假记录
- 排除实际的数据库问题
希望能使我们的单元测试更多"clean & seamless"
实际结果:
- 圆满成功
- 我们的测试非常干净、无缝且易于创建(见下文)
解决方案概要:
- 为每个存储库创建一个 MockBuilder(使用 Moq)
- 为 UnitOfWork 创建 MockBuilder(使用 Moq)
- 让对构建器的调用为我们消除所有复杂性
原始存储库界面:
这是我们所有存储库使用的接口。因此,我们所有 'builders' 都必须 Mock.Setup
每个存储库的所有这些。
public interface IRepository<TEntity> where TEntity : class
{
#region <Methods>
IQueryable<TEntity> GetActive();
IQueryable<TEntity> GetAll();
TEntity GetById(object id);
TEntity Find(params object[] keyValues);
void Add(TEntity entity);
void AddRange(IEnumerable<TEntity> entities);
void Update(TEntity entity);
void Delete(TEntity entity);
void Delete(object id);
void ApplyState(TEntity entity, EntityState state);
EntityState GetState(TEntity entity);
#endregion
}
生成器界面:
public interface IBuilder
{
// Marker interface
}
存储库模拟生成器界面:
public interface IRepositoryMockBuilder<TEntity> : IBuilder where TEntity : class
{
List<TEntity> Entities { get; }
EntityState EntityState { get; }
Mock<IRepository<TEntity>> CreateMock();
}
具体存储库模拟生成器:
public class ClaimRepositoryMockBuilder : IRepositoryMockBuilder<Claim>
{
#region <Constructor>
public ClaimRepositoryMockBuilder(bool autoSeed)
{
Entities = AutoSeed(autoSeed);
EntityState = EntityState.Unchanged;
}
#endregion
#region <Properties>
public List<Claim> Entities { get; private set; }
public EntityState EntityState { get; private set; }
#endregion
#region <Methods>
public Mock<IRepository<Claim>> CreateMock()
{
var repository = new Mock<IRepository<Claim>>();
repository.SetupAllProperties();
repository.Setup(x => x.GetActive()).Returns(this.Entities.AsQueryable());
repository.Setup(x => x.GetAll()).Returns(this.Entities.AsQueryable());
repository.Setup(x => x.GetById(It.IsAny<object>())).Returns((object id) => { return this.Entities.Where(e => e.ClaimId == id.ToString()).FirstOrDefault(); });
repository.Setup(x => x.Find(new object[] { It.IsAny<string>() })).Returns((object id) => { return this.Entities.Where(e => e.ClaimId == id.ToString()).FirstOrDefault(); });
repository.Setup(x => x.Add(It.IsAny<Claim>())).Callback<Claim>(x => { this.Entities.Add(x); }).Verifiable();
repository.Setup(x => x.AddRange(It.IsAny<IEnumerable<Claim>>())).Callback<IEnumerable<Claim>>(x => { this.Entities.AddRange(x); }).Verifiable();
repository.Setup(x => x.Update(It.IsAny<Claim>())).Callback<Claim>(x => { UpdateEntity(x); }).Verifiable();
repository.Setup(x => x.Delete(It.IsAny<Claim>())).Callback<Claim>(x => { DeleteByEntity(x); }).Verifiable();
repository.Setup(x => x.Delete(It.IsAny<object>())).Callback<object>(x => { DeleteById(x); }).Verifiable();
repository.Setup(x => x.ApplyState(It.IsAny<Claim>(), It.IsAny<EntityState>())).Callback<Claim, EntityState>((x, y) => { this.EntityState = y; }).Verifiable();
repository.Setup(x => x.GetState(It.IsAny<Claim>())).Returns((Claim claim) => { return this.EntityState; });
return repository;
}
#region private
private void DeleteById(object id)
{
var entity = this.Entities.FirstOrDefault(x => x.ClaimId == id.ToString());
if (entity != null)
this.Entities.RemoveAt(Entities.IndexOf(entity));
}
private void DeleteByEntity(Claim deletedEntity)
{
var entity = this.Entities.FirstOrDefault(x => x.ClaimId == deletedEntity.ClaimId);
if (entity != null)
this.Entities.Remove(entity);
}
private void UpdateEntity(Claim updatedEntity)
{
var entity = this.Entities.FirstOrDefault(x => x.ClaimId == updatedEntity.ClaimId);
if (entity != null)
entity = updatedEntity;
}
private List<Claim> AutoSeed(bool autoSeed)
{
if (!autoSeed)
return new List<Claim>();
var database = new List<Claim>();
database.Add(new Claim()
{
// Set Properties Here
});
database.Add(new Claim()
{
// Set Properties Here
});
database.Add(new Claim()
{
// Set Properties Here
});
return database;
}
#endregion
#endregion
}
具体工作单元模拟生成器:
添加与存储库一样多的属性(我们有 TON)
public class UnitOfWorkMockBuilder : IBuilder
{
#region <Constructors>
public UnitOfWorkMockBuilder(bool autoSeed)
{
ClaimsRepositoryBuilder = new ClaimsRepositoryMockBuilder(autoSeed);
SomeOtherRepositoryBuilder = new SomeOtherRepositoryMockBuilder(autoSeed);
}
#endregion
#region <Properties>
public ClaimsRepositoryMockBuilder ClaimsRepositoryBuilder { get; set; }
public SomeOtherRepositoryMockBuilder SomeOtherRepositoryBuilder { get; set; }
#endregion
#region <Methods>
public Mock<IMyUnitOfWork> CreateMock()
{
var unitOfWork = new Mock<IMyUnitOfWork>();
var depClaimTransactionsRepository = ClaimTransactionsRepositoryBuilder.CreateMock();
var depSomeOtherRepository = SomeOtherRepository.CreateMock();
unitOfWork.SetupAllProperties();
unitOfWork.Object.Claim = depClaimsRepositoryBuilder.Object;
unitOfWork.Object.SomeOther = depSomeOtherRepository.Object;
return unitOfWork;
}
#endregion
}
结果单元测试:
这只是一个测试,但是所有的测试都由于这项努力而得到了极大的清理。
[TestClass]
public class ClaimExistsRuleUnitTest
{
[TestMethod]
public void Returns_IsBau_When_Claim_DoesNotExist()
{
var builderUnitOfWork = new UnitOfWorkMockBuilder(true);
var claimId = "666";
var process = "MyAwesomeProcess";
// -----
// ARRANGE
// -----
var depUnitOfWork = builderUnitOfWork.CreateMock().Object;
var depProcess = depUnitOfWork.Processes.GetAll().Where(x => x.FriendlyName == process).First();
var depClaimMessage = new ClaimMessage();
var mockValidationResult = null as IValidationResult;
depClaimMessage.ClaimId = claimId;
depClaimMessage.StpClaimRequestProcessId = depProcess.Stp_Process_Code_Id;
// -----
// ACT
// -----
var rule = new ClaimExistsRule();
rule.UnitOfWork = depUnitOfWork;
mockValidationResult = rule.Validate(depClaimMessage);
// -----
// ASSERT
// -----
Assert.AreEqual(ClaimAction.IsBAU, mockValidationResult.Action);
}
#endregion
}
我希望在 运行 时动态创建一个 属性,以便安装程序可以将 属性 用于其数据 and/or 数据库值:
问题:
如何动态地将 属性 附加到模拟实例?
示例:
public Mock<IRepository<tClaims>> MockClaimsRepository()
{
var repository = new Mock<IRepository<tClaims>>();
// HOW DO I DO THIS?
repository.SetupProperty<Claim>(//* How do I append a dynamic property here *//)
repository.SetupProperty<List<Claims>>(//* How do I append a dynamic property here *//)
repository.Setup(x => x.GetActive()).Returns(repository.Claims.AsQueryable());
repository.Setup(x => x.GetAll()).Returns(repository.Claims.AsQueryable());
repository.Setup(x => x.GetById(repository.Claim.Claim_ID)).Returns(Claim);
repository.Setup(x => x.Find(new object[] { It.IsAny<object>() })).Returns(repository.Claim);
repository.Setup(x => x.Add(repository.Claim)).Verifiable();
repository.Setup(x => x.AddRange(repository.Claims)).Verifiable();
repository.Setup(x => x.Update(repository.Claim)).Verifiable();
repository.Setup(x => x.Delete(repository.Claim)).Verifiable();
repository.Setup(x => x.Delete(It.IsAny<object>())).Verifiable();
return repository;
}
您不能直接向模拟对象添加新属性,您可以使用具有所需属性的 As<>
方法添加另一个接口的实现。
但是如果您的目标是使用此属性为模拟提供逻辑和状态,那么您可以为此使用变量,如下所示:
public Mock<IRepository<Claims>> MockClaimsRepository()
{
var repository = new Mock<IRepository<Claims>>();
var claims = new List<Claims>(); // Add variables to be used in the setups
repository.Setup(x => x.GetAll()).Returns(claims.AsQueryable());
repository.Setup(x => x.GetById(It.IsAny<int>())).Returns<int>(id => claims.Find(c => c.Id == id));
repository.Setup(x => x.Add(It.IsAny<Claims>())).Callback<Claims>(c => claims.Add(c));
repository.Setup(x => x.Delete(It.IsAny<Claims>())).Callback<Claims>(c => claims.Remove(c));
...
return repository;
}
当您从 MockClaimsRepository
return 时,这些变量将不会被释放,因为您在设置中引用它。
您可以使用 SetupAllProperties() 吗?
Specifies that the all properties on the mock should have "property behavior", meaning that setting its value will cause it to be saved and later returned when the property is requested. (this is also known as "stubbing"). The default value for each property will be the one generated as specified by the property for the mock.
只是将 Arturo 更新为像您的存储库一样通用:
public Mock<IRepository<T>> MockRepository<T, TKey>() where T : BaseEntity<TKey> //(for Id )
{
var repository = new Mock<IRepository<T>>();
var entities = new List<T>(); // Add variables to be used in the setups
repository.Setup(x => x.GetAll()).Returns(entities.AsQueryable());
repository.Setup(x => x.GetById(It.IsAny<TKey>())).Returns<TKey>(id => entities.Find(c => c.Id == id));
repository.Setup(x => x.Add(It.IsAny<T>())).Callback<T>(c => entities.Add(c));
repository.Setup(x => x.Delete(It.IsAny<T>())).Callback<T>(c => entities.Remove(c));
...
return repository;
}
虽然 "basically" 正确,但我无法将给定答案中的任何一个标记为正确的原因是他们没有提供 "full" 解决问题的方法(例如,为测试引用自定义属性的能力 )。
可能有更好的方法来执行此操作,但这是对我有用的方法...
期望的结果:
- 模拟我的 UnitOfWork(使用最小起订量)
- 模拟我的 UnitOfWork 中的所有存储库(使用 Moq)
- 在模拟(如数据库)中使用来自属性的虚假记录
- 排除实际的数据库问题
希望能使我们的单元测试更多"clean & seamless"
实际结果:
- 圆满成功
- 我们的测试非常干净、无缝且易于创建(见下文)
解决方案概要:
- 为每个存储库创建一个 MockBuilder(使用 Moq)
- 为 UnitOfWork 创建 MockBuilder(使用 Moq)
- 让对构建器的调用为我们消除所有复杂性
原始存储库界面:
这是我们所有存储库使用的接口。因此,我们所有 'builders' 都必须 Mock.Setup
每个存储库的所有这些。
public interface IRepository<TEntity> where TEntity : class
{
#region <Methods>
IQueryable<TEntity> GetActive();
IQueryable<TEntity> GetAll();
TEntity GetById(object id);
TEntity Find(params object[] keyValues);
void Add(TEntity entity);
void AddRange(IEnumerable<TEntity> entities);
void Update(TEntity entity);
void Delete(TEntity entity);
void Delete(object id);
void ApplyState(TEntity entity, EntityState state);
EntityState GetState(TEntity entity);
#endregion
}
生成器界面:
public interface IBuilder
{
// Marker interface
}
存储库模拟生成器界面:
public interface IRepositoryMockBuilder<TEntity> : IBuilder where TEntity : class
{
List<TEntity> Entities { get; }
EntityState EntityState { get; }
Mock<IRepository<TEntity>> CreateMock();
}
具体存储库模拟生成器:
public class ClaimRepositoryMockBuilder : IRepositoryMockBuilder<Claim>
{
#region <Constructor>
public ClaimRepositoryMockBuilder(bool autoSeed)
{
Entities = AutoSeed(autoSeed);
EntityState = EntityState.Unchanged;
}
#endregion
#region <Properties>
public List<Claim> Entities { get; private set; }
public EntityState EntityState { get; private set; }
#endregion
#region <Methods>
public Mock<IRepository<Claim>> CreateMock()
{
var repository = new Mock<IRepository<Claim>>();
repository.SetupAllProperties();
repository.Setup(x => x.GetActive()).Returns(this.Entities.AsQueryable());
repository.Setup(x => x.GetAll()).Returns(this.Entities.AsQueryable());
repository.Setup(x => x.GetById(It.IsAny<object>())).Returns((object id) => { return this.Entities.Where(e => e.ClaimId == id.ToString()).FirstOrDefault(); });
repository.Setup(x => x.Find(new object[] { It.IsAny<string>() })).Returns((object id) => { return this.Entities.Where(e => e.ClaimId == id.ToString()).FirstOrDefault(); });
repository.Setup(x => x.Add(It.IsAny<Claim>())).Callback<Claim>(x => { this.Entities.Add(x); }).Verifiable();
repository.Setup(x => x.AddRange(It.IsAny<IEnumerable<Claim>>())).Callback<IEnumerable<Claim>>(x => { this.Entities.AddRange(x); }).Verifiable();
repository.Setup(x => x.Update(It.IsAny<Claim>())).Callback<Claim>(x => { UpdateEntity(x); }).Verifiable();
repository.Setup(x => x.Delete(It.IsAny<Claim>())).Callback<Claim>(x => { DeleteByEntity(x); }).Verifiable();
repository.Setup(x => x.Delete(It.IsAny<object>())).Callback<object>(x => { DeleteById(x); }).Verifiable();
repository.Setup(x => x.ApplyState(It.IsAny<Claim>(), It.IsAny<EntityState>())).Callback<Claim, EntityState>((x, y) => { this.EntityState = y; }).Verifiable();
repository.Setup(x => x.GetState(It.IsAny<Claim>())).Returns((Claim claim) => { return this.EntityState; });
return repository;
}
#region private
private void DeleteById(object id)
{
var entity = this.Entities.FirstOrDefault(x => x.ClaimId == id.ToString());
if (entity != null)
this.Entities.RemoveAt(Entities.IndexOf(entity));
}
private void DeleteByEntity(Claim deletedEntity)
{
var entity = this.Entities.FirstOrDefault(x => x.ClaimId == deletedEntity.ClaimId);
if (entity != null)
this.Entities.Remove(entity);
}
private void UpdateEntity(Claim updatedEntity)
{
var entity = this.Entities.FirstOrDefault(x => x.ClaimId == updatedEntity.ClaimId);
if (entity != null)
entity = updatedEntity;
}
private List<Claim> AutoSeed(bool autoSeed)
{
if (!autoSeed)
return new List<Claim>();
var database = new List<Claim>();
database.Add(new Claim()
{
// Set Properties Here
});
database.Add(new Claim()
{
// Set Properties Here
});
database.Add(new Claim()
{
// Set Properties Here
});
return database;
}
#endregion
#endregion
}
具体工作单元模拟生成器:
添加与存储库一样多的属性(我们有 TON)
public class UnitOfWorkMockBuilder : IBuilder
{
#region <Constructors>
public UnitOfWorkMockBuilder(bool autoSeed)
{
ClaimsRepositoryBuilder = new ClaimsRepositoryMockBuilder(autoSeed);
SomeOtherRepositoryBuilder = new SomeOtherRepositoryMockBuilder(autoSeed);
}
#endregion
#region <Properties>
public ClaimsRepositoryMockBuilder ClaimsRepositoryBuilder { get; set; }
public SomeOtherRepositoryMockBuilder SomeOtherRepositoryBuilder { get; set; }
#endregion
#region <Methods>
public Mock<IMyUnitOfWork> CreateMock()
{
var unitOfWork = new Mock<IMyUnitOfWork>();
var depClaimTransactionsRepository = ClaimTransactionsRepositoryBuilder.CreateMock();
var depSomeOtherRepository = SomeOtherRepository.CreateMock();
unitOfWork.SetupAllProperties();
unitOfWork.Object.Claim = depClaimsRepositoryBuilder.Object;
unitOfWork.Object.SomeOther = depSomeOtherRepository.Object;
return unitOfWork;
}
#endregion
}
结果单元测试:
这只是一个测试,但是所有的测试都由于这项努力而得到了极大的清理。
[TestClass]
public class ClaimExistsRuleUnitTest
{
[TestMethod]
public void Returns_IsBau_When_Claim_DoesNotExist()
{
var builderUnitOfWork = new UnitOfWorkMockBuilder(true);
var claimId = "666";
var process = "MyAwesomeProcess";
// -----
// ARRANGE
// -----
var depUnitOfWork = builderUnitOfWork.CreateMock().Object;
var depProcess = depUnitOfWork.Processes.GetAll().Where(x => x.FriendlyName == process).First();
var depClaimMessage = new ClaimMessage();
var mockValidationResult = null as IValidationResult;
depClaimMessage.ClaimId = claimId;
depClaimMessage.StpClaimRequestProcessId = depProcess.Stp_Process_Code_Id;
// -----
// ACT
// -----
var rule = new ClaimExistsRule();
rule.UnitOfWork = depUnitOfWork;
mockValidationResult = rule.Validate(depClaimMessage);
// -----
// ASSERT
// -----
Assert.AreEqual(ClaimAction.IsBAU, mockValidationResult.Action);
}
#endregion
}