Your mission, should you choose to accept it, is to observe the interaction with an object and verify that this interaction is in your best interest.
The Scenario
Some objects that you have to consume are just poorly written. The ones that are most egregious always seem to be the ones you have no control over. That lack of control may be because you have no access to the source, it may be because it would be a political minefield to change the source, or a lack of tests makes the team afraid to change the source. It seems like every time I go to a new client, these objects exist (as well as the political minefields). The developers have a mysterious set of incantations that they have memorized for interaction with these objects in order to avoid bugs. Often, no one knows where these "rules" came from and they are usually not written down.
The Vocabulary
The name of this pattern is the Spy. What the spy will do is capture information about interaction with an object, and only take action if the need arises. A spy object looks just like the object that you need to interact with (i.e. implements the same public interface) such that your code should not even notice it is even there, but, behind the scenes it will perform validation and give the useful feedback you wish the original object would have implemented in the first place. This spy or validation wrapper is commonly implemented by holding on to a reference to original object. This allows for the spy to call the actual implementation in order to preserve the original behavior.
The ideal world
In an ideal world, the kinds of interactions that you are trying to validate with a spy should be captured be captured by the actual object you are interacting with, rather than the spy. Having a wrapper object whose function is validation is a massive code smell. If you have the ability to fix the original code by adding the relevant validation, that is by far a better solution than creating a spy object.
The real world
In reality, you do not have access to change the source of third party libraries, even if sometimes that third party is a couple of buildings or even cubes away. The first thing you should do when you run in to these bizarre incantations “required” for successful object interaction, is to ask “why?” and be persistent, dig deep. You may be (not so) surprised that most of the reasons have long since gone away. If you do find that some of the hidden rules are indeed valid, you need a way to validate that your code is following the rules.
Example
Consider the following example where MethodToObserve will throw an uninformitive exception if the PropertyToObserve has not yet been set.
1 public interface IInterfaceToUse
2 {
3 void MethodToObserve();
4 List<string> PropertyToObserve { get; set; }
5 }
6
7 public class ClassToUse : IInterfaceToUse
8 {
9 public void MethodToObserve()
10 {
11 PropertyToObserve.ForEach(str =>
12 Console.WriteLine("Calling the MethodToObserve:" + str) );
13 }
14
15 private List<string> propertyToObserve;
16 public List<string> PropertyToObserve
17 {
18 get
19 {
20 Console.WriteLine( "Calling the PropertyToObserve setter");
21 return propertyToObserve;
22 }
23 set
24 {
25 Console.WriteLine("Calling the PropertyToObserve getter");
26 propertyToObserve = value;
27 }
28 }
29 }
As mentioned previously, the ideal solution is to fix the implementation. If your only access to this code is Reflector or you are just not authorized to change it, the next best thing is to protect yourself (flaming email to the author of the code is,of course, optional). Our protection, or at least better information will come from a class implementing IInterfaceToUse just like the original, only this time the implementation will provide the consumer with information that they can act on.
1 public class ValidatingObserver : IInterfaceToUse
2 {
3 private IInterfaceToUse _observedClass;
4
5 public ValidatingObserver(IInterfaceToUse observedClass)
6 { _observedClass = observedClass; }
7
8 public void MethodToObserve()
9 {
10 if (PropertyToObserve == null)
11 throw new ArgumentNullException("PropertyToObserve",
12 "Property must be set prior to calling Method");
13 // perform observations
14 Console.WriteLine("The spy is watching: MethodToObserve");
15
16 // pass through to implementing object
17 _observedClass.MethodToObserve();
18 }
19
20 public List<string> PropertyToObserve
21 {
22 get
23 {
24 Console.WriteLine("The spy is watching: PropertyToObserve getter");
25 return _observedClass.PropertyToObserve;
26 }
27 set
28 {
29 Console.WriteLine("The spy is watching: PropertyToObserve setter");
30 _observedClass.PropertyToObserve = value;
31 }
32 }
33 }
Note that this time, instead of the “oops, I forgot something exception” known in .net as the “Object reference not set to instance of an object” exception, we get meaningful information about what is missing and even some hint as to how to fix it. The error now clearly states that the PropertyToObserve should be set prior to calling the MethodToObserve.
Using the spy
There are many ways to create a spy object, I chose containment for this post; You may also use derivation to create your wrapper. Derivation will get you up and running faster, and you will not have maintenance work to do if you add a method to the interface, but this will come at a cost. Containment will allow you to swap out the actual implementation of the object with a mock implementation at some time in the future. As always consider you needs before choosing a spy implementation.
The test below shows how to use the spy created in this post
1 [TestFixture]
2 class ManualObserverTests
3 {
4 [Test]
5 public void MethodCallSpy()
6 {
7 var observedClass = new ClassToUse();
8 var validatingObserver = new ValidatingObserver(observedClass);
9
10 Assert.Throws<ArgumentNullException>( validatingObserver.MethodToObserve);
11 }
12 }
Tool Support
All of the previous posts in this series have mentioned leveraging tools to assist in creating these test objects. They spy object however is a strange beast; the demands it places on the tools turn out to create as much code as the manually coded version. If you are so inclined, you can use Rhino for a spy object. What is required is taking advantage of the Do extension method. Do takes a delegate as a parameter that matches the signature of the method being called. So what you will end up establishing is an Expectation that a method will be called and when it is Do the operation specified by the delegate.
Summary
The spy object reminds me of the Broken Windows section of the Pragmatic Programmer. It clearly states not to live with broken windows, but if you cannot fix the window, at least put a board over the window. In the case of third party code where you cannot change the source code, a validation wrapper is the board you need to keep further damage from occurring and show other developers in the area that you still care about the quality of code.
The Series
PatternsInTesting[2] - Stub Pattern PatternsInTesting[4] - Mock Pattern