Problem: When you are doing unit testing you are often faced with testing against some mock repository or some object store. The actual repository could be SQL repository or Azure repository or even CSV files . Inside a unit test you want some fake data and that data is easy to come up with as long you don’t have many records and fewer properties on each of those objects. For example, in the code below if you want to test IPersonRepository’s GetAll method you will try to write dummy data as a List<Person>(). I am using FakeItEasy as my mocking framework.
var repo = A.Fake();
var people = new List(){
new Person {FirstName = "Mitul", LastName = "Suthar"},
new Person {FirstName = "Scott", LastName = "Smith"}
};
A.CallTo(() => repo.GetAll()).Returns(people);
1. What if you have more properties than just firstName and LastName? Typically in a real world you will have lots of properties and lot of different values which you will have tough time creating and remembering for your particular unit test.
2. What if you have entities that are related? While IPersonRepository’s GetAll() method is very basic but what if you want to test FindAllPeopleWhoDidNotPayBills() in which you are doing a LINQ query against different related tables and want to test if that LINQ query is returns appropriate data. That LINQ query might look something like this.
var peopleWhoDidNotPayBills = from p in Persons
join s in Sales on p.ID equals s.PersonId
select new { p.FirstName, p.LastName, p.Email, s.OutStandingAmt};
How are you going to generate fake data if you want to test this query. A solution is you want to generate your own DbSet<Person> and DbSet<Sale> and you would make sure that the data would be related in those classes. Good luck coming up with that.
Generally people would do data driven tests that will pull data from database, excel, csv file. At my work in our current application we have lots of existing tables (think more than 100) and lots of columns (more than 20) on each of those tables and it is just painful trying to hook up each table and property using our List initializer syntax. And on top of that our unit test would have become very slow. In order to speed things up you want to have all your sample data hard coded in some helper method.
For each DbSet<T> we have a helper method that generates a DbSet<T> from hard coded List<T>. That hard coded List<T> is generated automatically using LINQPad Extension method. In our team we love LINQPad and I wished if there was a way to generate List<T> based upon the LINQ query that we execute inside LINQPad. So here is the extension method.
Copy and paste inside your MyExtensions static class in My Queries section of LINQPad. If you write any extension method here it is available throughout LINQPad.
public static object GetClist(this object value, string className)
{
var enumerator = (IList)value;
Type typeList = value.GetType().GetGenericArguments()[0];
var listBuilder = new StringBuilder();
listBuilder.AppendLine(String.Format("new List<{0}>() {{",className));
foreach (dynamic element in enumerator)
{
Type type = element.GetType();
var classBuilder = new StringBuilder();
classBuilder.AppendLine(String.Format(@"new {0}() {{", className));
ListlstElements = new List ();
foreach (PropertyInfo p in type.GetProperties())
{
if (p.GetValue(element) != null)
{
if (p.GetValue(element).GetType().Name =="DateTime")
{
lstElements.Add(String.Format(@"{0} = Convert.ToDateTime(""{1}"")", p.Name, p.GetValue(element)));
}else if(p.GetValue(element).GetType().Name == "String"){
lstElements.Add(String.Format(@"{0} = ""{1}""", p.Name, p.GetValue(element)));
}else if(p.GetValue(element).GetType().Name == "Decimal"){
lstElements.Add(String.Format(@"{0} = {1}",p.Name, p.GetValue(element)));
}else if(p.GetValue(element).GetType().Name == "Int32"){
lstElements.Add(String.Format(@"{0} = {1}",p.Name, p.GetValue(element)));
}else if(p.GetValue(element).GetType().Name == "Boolean"){
lstElements.Add(String.Format(@"{0} = {1}",p.Name, p.GetValue(element)));
}else if(p.GetValue(element).GetType().Name == "Guid"){
lstElements.Add(String.Format(@"{0} = new Guid(""{1}"")",p.Name, p.GetValue(element)));
}else{
//Console.WriteLine("{0} = {1}, baseType: {2}", p.Name, p.GetValue(element), p.GetValue(element).GetType().Name);
}
}
}
for(int i = 0; i < lstElements.Count;i++){
if (i == lstElements.Count - 1)
{
classBuilder.Append(String.Format("{0}",(lstElements.ToList()[i].ToString())));
}else{
classBuilder.AppendLine(String.Format("{0},",(lstElements.ToList()[i].ToString())));
}
}
listBuilder.AppendLine(classBuilder.ToString());
listBuilder.AppendLine(@"},");
}
var preFinal = listBuilder.ToString().Remove(listBuilder.ToString().LastIndexOf(','), 1);
var finalValue = String.Format(@"{0}}};", preFinal);
Console.WriteLine(finalValue);
return value;
}
On how to use it.
1. Save your query. On saving it gets compiled and it gives you warning if you didn’t something wrong.
2. In LINQPad any anonymous query will not just work. You will have to do a ToList() and then GetCList(“Nameofclass”) method to get List<T> as C# text which you can copy and paste.
3. Don’t forget to specify the type of the class you want to generate in the GetCList(“Person”);
4. Now you can copy that the generated list and then use it in your dummy data generator class.
5. If you want to add support for different data types then first you might want to know which property is not showing up. Just uncomment this line in the extension and it will display which type is not listed.
//Console.WriteLine("{0} = {1}, baseType: {2}", p.Name, p.GetValue(element), p.GetValue(element).GetType().Name);
Then just add another if else and generate your own type initializer.
Since this is a LINQPad extension you can do a lot of mix and match and generate a lot sample data as C# text pretty quickly. I am using AdventureWorks and using GetCList method against Persons. This is how it looks like.
var people = from p in Persons
select new
{
p.BusinessEntityID,
p.PersonType,
p.NameStyle,
p.Title,
p.FirstName,
p.MiddleName,
p.LastName,
p.Suffix,
p.EmailPromotion,
p.AdditionalContactInfo,
p.Demographics,
p.Rowguid,
p.ModifiedDate
};
people.Take(2).ToList().GetClist("Person");
And the generated C# List is pretty basic nothing fancy here. But this is generated using the extension method.
new List() {
new Person() {
BusinessEntityID = 1,
PersonType = "EM",
NameStyle = False,
FirstName = "Ken",
MiddleName = "J",
LastName = "Sánchez",
EmailPromotion = 0,
Rowguid = new Guid("92c4279f-1207-48a3-8448-4636514eb7e2"),
ModifiedDate = Convert.ToDateTime("2/8/2003 12:00:00 AM")
},
new Person() {
BusinessEntityID = 2,
PersonType = "EM",
NameStyle = False,
FirstName = "Terri",
MiddleName = "Lee",
LastName = "Duffy",
EmailPromotion = 1,
Rowguid = new Guid("d8763459-8aa8-47cc-aff7-c9079af79033"),
ModifiedDate = Convert.ToDateTime("2/24/2002 12:00:00 AM")
}
};
Another pain point in this is if you are generating DataTime then getting the right DateTime for that particular event is tough. You have to think through different events that might have happened with that entity. For example, Lastvisited, ApprovedOn, InactivatedOn, PaymentPostedDate, etc. These dates have to be related in real world business problems. By generating data this way you are sure it will be related and you don’t have to think about it.
In LINQ using the let keyword you can generate different sets of data easily. You can generate related DbSet<T> in LINQPad easily but coming up related data manually is very time consuming and boring. If you made this far in a very silly post then thanks for that. Please let me how you are testing against DbSet<T> with lots of properties and if this could be improved upon.
No comments:
Post a Comment