I am writing this as a way to document something that is possible, but you probably shouldn’t do unless you really need to. Also, I’m sure this is not new, I just can’t find any mention of it on Google right now. Which probably means it is a bad idea. After all, if the Internet, the keeper of all that is right and true, doesn’t know about something it must be wrong.
The problem
You have two methods (Foo and Bar), and Foo calls method Bar. It will look like this:
public int Foo() { // stuff happens here int i = Bar(); // do more stuff here
return i + 5
} public int Bar() { // do some logic return 1; }
Now you need to test both methods. Bar can be easily tested like this (this should work for NUnit and MBUnit tests — assuming both methods exist in a class call MyClass):
[Test] public void Bar_Test() { MyClass c = new MyClass(); int result = c.Bar(); Assert.AreEqual(1, result); }
Now we test Foo:
[Test] public void Foo_Test() { MyClass c = new MyClass(); int result = c.Foo(); Assert.AreEqual(6, result); }
Here is where we have an issue. What if the result of Bar changes? That will break the test. So now we have a brittle test. Any time Bar changes, we have to change all of the Bar tests, but also the Foo tests, so we have added complexity to our tests as well. Now if Foo (or Bar) was in a separate class, this wouldn’t be an issue, but they are in the same class. And as much as I like small classes, this seems too trivial to split up (even in the context of an extremely trivial example).
The Solution
What I would like to do is Mock the method Bar. You can do that with a delegate.
Without really getting into what a delegate is, I will just show the code. So here are my two methods, with a delegate added in for good measure.
public delegate int BarDelegate(); public BarDelegate Bar; public MyClass() { Bar = BarInternal; } public int Foo() { // stuff happens here int i = Bar(); // do more stuff here return i + 5; } public int BarInternal() { // do some logic return 1; }
OK, here is what I did:
- Renamed Bar to BarInternal
- BarDelegate delegate
- Created a variable named Bar to point to the method BarInternal
- Created a constructor to hook up Bar to BarInternal.
Now all of my code can still use the method Bar just like it was there, but in actuality it is calling BarInternal. I have now created one layer of indirection to my method Bar so it can now be mocked and tested.
Here is my new test for Foo:
[Test] public void FooTest() { MyClass c = new MyClass() c.Bar = BarMethodForTest; int result = target.Foo(); Assert.AreEqual(7, result); } private int BarMethodForTest() { return 2; }
So what is new with the test is the BarMethodForTest that is passed to c.Bar and I have coded BarMethodForTest to always return 2. When I set c.Bar to my new method, BarInternal (the default method) is no longer called.
Actually, if I wanted to I could simplify this further by setting c.Bar with an anonymous delegate like this:
[Test] public void FooTest() { MyClass c = new MyClass() c.Bar = delegate() { return 2; } int result = target.Foo(); Assert.AreEqual(7, result); }
Stepping Back
OK, take a step back and look at this again with a critical eye. Have I really solved the problem? Yes and no. The real problem was that my methods were too complex to simply test. Now my methods are simple to test, but the code is complex to read. All I’ve done is shifted the complexity from one class to the other. A better solution would to have created another class and further abstracted out the logic.
At the same time, my class is not overly burdened by the change either. In lieu of a real refactoring, I do consider this a viable solution to the problem. So by all means do this — but make sure you are out of alternatives first.
In these cases, I will generally prefer to make Bar virtual and to override it in a mock subclass.
@Jeff – Thank you for the reply. I did forget about that approach.
So in this case you create your class as normal, but make most of you methods virtual. Then, in your test library, create a class that inherits from the class you are testing, and override the methods you need to replace.
Is that it?
BTW: I think you miss-typed your blog, should have been: http://blog.bits-in-motion.com/