We were writing some tests that need a couple instances of a data object. Well, my first test new-ed up the instance and added it to the collection. This instance needed to have the due date property set, so I did something like this:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
List<MyThing> things = new List<MyThing> { | |
new MyThing { DueDate = DateTime.Now.AddDays(1), | |
new MyThing { DueDate = DateTime.Now.AddDays(-14)}} | |
}; |
Well, there's a bit of annoying duplication there, so I changed it to:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
List<MyThing> things = new List<MyThing> { | |
thing(DateTime.Now.AddDays(1)), | |
thing(DateTime.Now.AddDays(-14)) | |
} |
with a method later on that looks like this
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public MyThing thing(DateTime duedate){ | |
return new MyThing {DueDate = duedate}; | |
} |
Okay, a bit better.
A little bit later, we had another test fixture that used the data object in a similar way, so I was looking for a way to bring the thing() method into a scope that was available for the other fixture, as well. When I was doing 2.0, I would have probably built a base class for my test fixtures and subclasses it in both fixtures. Yuk! Those who know me know that I have a special, black, cold place in my heart for subclassing like this.
Enter extension methods
I want a way to have some utility methods that I can use whenever I have a fixture that needs to build things. Aha! Let's make an interface:
public interface TestsThings {}
And, add an extension method to it:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public static class ThingTestingUtilities { | |
public static Thing NewThing(this TestsThings tester, DateTime duedate) { | |
return new MyThing { DueDate = duedate}; | |
} | |
} |
Cool. Now, my test fixtures can implement TestsThings and get the method, so the construction of the lists will now look like this:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
List<MyThing> things = new List<MyThing> { | |
this.NewThing(DateTime.Now.AddDays(1)), | |
this.NewThing(DateTime.Now.AddDays(-14)) | |
} |
Unnecessary Construction Parameters? Yuk!
Well, not happy to leave a bad thing bad, I decided to take it one step further. I want to be able to write
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
List<MyThing> things = new List<MyThing> { | |
this.NewThing().WithDueDate(DateTime.Now.AddDays(1)), | |
this.NewThing().WithDueDate(DateTime.Now.AddDays(-14)) | |
} |
How to do that? Hmm.... Ah, here we go. Let's change ThingTestingUtilities to look like this:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public static class ThingTestingUtilities { | |
public static Thing NewThing(this TestsThings tester) { | |
return new MyThing; | |
} | |
public static Thing WithDueDate(this MyThing thing, DateTime duedate) { | |
thing.DueDate = duedate; | |
return thing; | |
} | |
} |
Works much better. But, there is still a matter of duplication in the DateTime.Now... stuff. So, let's take this a step further. What if I could write this:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
List<MyThing> things = new List<MyThing> { | |
this.NewThing().WithAnOffsetDueDateInDays(1), | |
this.NewThing().WithAnOffsetDueDateInDays(-14) | |
} |
That looks much better. Yeah, if I was in a different language, I'd do something like:
this.NewThings().WithAnOffsetDueDate(1.days);
[Update: Steven Harman alerted me that you can do this. I'm too lazy right now to implement it, though. Maybe later]
[Update2: Leon Gersin added a comment showing how to extend it like 1.days. Check it out.]
So, let's add a helper method. The utility class looks like this now:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public static class ThingTestingUtilities { | |
public static Thing NewThing(this TestsThings tester) { | |
return new MyThing; | |
} | |
public static Thing WithDueDate(this MyThing thing, DateTime duedate) { | |
thing.DueDate = duedate; | |
return thing; | |
} | |
public static Thing WithAnOffsetDueDateInDays(this MyThing thing, int days){ | |
return thing.WithDueDate(DateTime.Now.AddDays(days)); | |
} | |
} |
Cool, compiles, runs, now my construction looks like this:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
List<MyThing> things = new List<MyThing> { | |
this.NewThing().WithAnOffsetDueDateInDays(1), | |
this.NewThing().WithAnOffsetDueDateInDays(-14) | |
} |
Extension methods can definitely help do some neat stuff. I don't have to subclass, I can add methods by simply implementing an interface. This also gives me the ability to create interfaces that are focused on different tasks that my test fixtures might need, so I don't have to load up subclasses with unnecessary mixes of unrelated methods.