TDD – MockRepositories for WCF Service Factory
Posted by Vlad on September 21, 2007
When testing the business logic layer you need to fake the data access layer so you have to worry about the database or the connection speed. The following example focuses on the Repository pattern used in the WCF Service Factory, but it can be adapted to other data access layer architectures.
What do you need from a MockRepository?
· To know if certain methods have been called.
· To know what parameters were passed to those methods.
· To know what (fake) results were returned.
The base Repository class that the Data Access Guidance Package helps you create has 6 types of methods: SearchOne, SearchMany, SearchAll, Insert, Update and Delete. The derived Repository classes use these to accomplish various tasks (SearchById, SearchByIDParent, etc). For the tests, we’ll use the same model: a base MockRepository class that will be inherited by the actual MockRepositories. In order to be able to use them instead of the real ones, we’ll extract the interfaces from the real repositories, have the MockRepositories implement them, and have the business actions work with these interfaces instead.
The base MockRepository class manages an internal list to simulate a repository:
public class MockRepository<TDomainObject>
{
private List<TDomainObject> _internalList;
public List<TDomainObject> InternalList
{
get
{
return _internalList;
}
set { _internalList = value; }
}
public MockRepository()
{
InternalList = new List<TDomainObject>();
}
public int CCAdd;
public TDomainObject LastAdded = default(TDomainObject);
public virtual void Add(TDomainObject toAdd)
{
CCAdd++;
InternalList.Add(toAdd);
LastAdded = toAdd;
}
public int CCRemove;
public TDomainObject LastRemoved = default(TDomainObject);
public virtual void Remove(TDomainObject toRemove)
{
CCRemove++;
InternalList.Remove(toRemove);
LastRemoved = toRemove;
}
public virtual void Remove(Predicate<TDomainObject> match)
{
CCRemove++;
LastRemoved = InternalList.Find(match);
InternalList.Remove(LastRemoved);
}
public int CCSearchOne;
public TDomainObject LastSearchOne = default(TDomainObject);
public virtual TDomainObject SearchOne(Predicate<TDomainObject> match)
{
CCSearchOne++;
LastSearchOne = InternalList.Find(match);
return LastSearchOne;
}
public int CCSave;
public TDomainObject LastSaveOriginal = default(TDomainObject);
public TDomainObject LastSaveNew = default(TDomainObject);
public virtual void Save(TDomainObject toSave, Predicate<TDomainObject> match)
{
CCSave++;
LastSaveOriginal = InternalList.Find(match);
if (LastSaveOriginal.Equals(default(TDomainObject)))
throw new InvalidOperationException(“The object to be saved could not be found.”);
InternalList[InternalList.IndexOf(LastSaveOriginal)] = toSave;
LastSaveNew = toSave;
}
public int CCSearchMany;
public List<TDomainObject> LastSearchMany = null;
public virtual List<TDomainObject> SearchMany(Predicate<TDomainObject> match)
{
CCSearchMany++;
LastSearchMany = InternalList.FindAll(match);
return LastSearchMany;
}
public int CCSearchAll;
public virtual List<TDomainObject> SearchAll()
{
CCSearchAll++;
return InternalList;
}
}
The derived MockRepositories can implement the methods from the real repository’s interface:
public Question GetQuestionByID(Guid iD)
{
return base.SearchOne(delegate(Question target) { return target.ID == iD; });
}
If necessary, they can add other “flag fields” to be used in tests.
In order to use them in tests, just pass them to the business action instead of the real ones and validate the operation using the Call Count fields (CCMethodName) and/or the Last____ fields for parameters and return values.
MyAction action = new MyAction();
MockQuestionRepository questionRep = new MockQuestionRepository();
MockResultRepository resultRep = new MockResultRepository();
action.QuestionRepository = questionRep;
action.ResultRepository = resultRep;
Result result = action.Execute();
Assert.IsNotNull(result);
Assert.AreEqual(resultRep.LastSearchOne, result);
Assert.AreEqual(1, questionRep.CCAdd);