Reputation: 14176
I am hoping to create a property dynamically at run-time so the Setup can use THAT PROPERTY for its data and/or database value(s):
QUESTION:
How can I append a property to the MOCK INSTANCE dynamically?
EXAMPLE:
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;
}
Upvotes: 1
Views: 1300
Reputation: 14176
While, "basically" correct, the reason I cannot mark either of the given answers as correct is they didn't offer-up a "full" solution to the problem (e.g. the ability to reference custom properties for testing).
There may be a better way to do this, but here is what worked for me...
DESIRED OUTCOME:
The hope was to make our Unit Tests much more "clean & seamless"
ACTUAL OUTCOME:
OUTLINE OF THE SOLUTION:
ORIGINAL REPOSITORY INTERFACE:
This is the interface all our repositories use. So, all our 'builders' had to Mock.Setup
all of these for each repository.
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
}
BUILDER INTERFACE:
public interface IBuilder
{
// Marker interface
}
REPOSITORY MOCK BUILDER INTERFACE:
public interface IRepositoryMockBuilder<TEntity> : IBuilder where TEntity : class
{
List<TEntity> Entities { get; }
EntityState EntityState { get; }
Mock<IRepository<TEntity>> CreateMock();
}
CONCRETE REPOSITORY MOCK BUILDER:
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
}
CONCRETE UNIT-OF-WORK MOCK BUILDER:
Add as many properties as you have repositories (we have a 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
}
THE RESULTING UNIT TEST:
This is just one test, but ALL of out tests GREATLY cleaned up as a result of this effort.
[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
}
Upvotes: 0
Reputation: 84
You can use 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.
Just to update Arturo to be generic like your repository:
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;
}
Upvotes: 0
Reputation: 15982
You can't directly add new properties to the mocked object, what you can do is add an implementation of another interface using As<>
method with the properties you want.
But if your goal is use this properties to give logic and state to the mock, then you can use variables for that, something like this:
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;
}
These variables will not be disposed when yo return from MockClaimsRepository
since you are referencing it in the setups.
Upvotes: 1