As a smoker, allow me to tell you this, Dependencies are NOT cool! If someone had offered me an easy way to cut my dependency on cigarettes I’d jump on it.
Sadly for me, there is no easy way to do that, but luckily for you, in some cases dropping down dependencies is possible, it’s easy and it even has an added value, at least when it comes to unit testing.
Consider this simple case:
1 |
<span id="lnum1" class="lnum"> 1:</span> <span class="kwrd">public</span> <span class="kwrd">class</span> MyType |
1 |
<span id="lnum2" class="lnum"> 2:</span> { |
1 |
<span id="lnum3" class="lnum"> 3:</span> <span class="kwrd">private</span> Dependency dep; |
1 |
<span id="lnum4" class="lnum"> 4:</span> |
1 |
<span id="lnum5" class="lnum"> 5:</span> <span class="kwrd">public</span> MyType(Dependency inDep) |
1 |
<span id="lnum6" class="lnum"> 6:</span> { |
1 |
<span id="lnum7" class="lnum"> 7:</span> dep = inDep; |
1 |
<span id="lnum8" class="lnum"> 8:</span> } |
1 |
<span id="lnum9" class="lnum"> 9:</span> |
1 |
<span id="lnum10" class="lnum"> 10:</span> <span class="kwrd">public</span> <span class="kwrd">int</span> Sum(<span class="kwrd">int</span> a, <span class="kwrd">int</span> b) |
1 |
<span id="lnum11" class="lnum"> 11:</span> { |
1 |
<span id="lnum12" class="lnum"> 12:</span> <span class="kwrd">return</span> dep.Sum(a, b); |
1 |
<span id="lnum13" class="lnum"> 13:</span> } |
1 |
<span id="lnum14" class="lnum"> 14:</span> } |
1 |
<span id="lnum15" class="lnum"> 15:</span> |
1 |
<span id="lnum16" class="lnum"> 16:</span> |
1 |
<span id="lnum17" class="lnum"> 17:</span> public <span class="kwrd">class</span> Dependency |
1 |
<span id="lnum18" class="lnum"> 18:</span> { |
1 |
<span id="lnum19" class="lnum"> 19:</span> <span class="kwrd">public</span> <span class="kwrd">int</span> Sum(<span class="kwrd">int</span> a, <span class="kwrd">int</span> b) |
1 |
<span id="lnum20" class="lnum"> 20:</span> { |
1 |
<span id="lnum21" class="lnum"> 21:</span> <span class="kwrd">return</span> a + b; |
1 |
<span id="lnum22" class="lnum"> 22:</span> } |
1 |
<span id="lnum23" class="lnum"> 23:</span> } |
1 |
|
1 |
1 |
Lets say that you need to instantiate MyType for your test.In order to do so you first need to have an instance or a fake of Dependency class (as required by MyType’s Ctor ).
As stated earlier, this example is a simple one. In reality, there would be more than one dependency, and each dependency is most likely dependent on other classes itself. The plot gets thicker.
OK, back to our case. What you would normally do in order to test MyType is first deal with the types it depends on.
Faking Dependency and passing it to MyType’s constructor will do the trick,
Dependency dp = Isolate.Fake.Instance<Dependency>();
MyType mt = new MyType(dp);
Sure it seems harmless, but don’t forget that in reality, where the types are really complex and writing surplus code will really cost you time (and someone else’s real money), it wouldn’t take long to decide to skip this test case.
So let me introduce you to an easy way of handling dependencies, it’s called Isolate.Fake.Dependencies<>();
Using this new API you can Instantiate classes without being worried about their dependencies.
MyType mt = Isolate.Fake.Dependencies<MyType>();
Is all you have to do in order to Instantiate MyType. That easy!
A little peek behind scenes. What actually happens is that Isolator recognizes the types of the constructor argument and acts as follows:
– If a type is a value type – its default value is set.
– If a type is a reference type – it is being recursively faked.
Isolate.Fake.Dependencies calls the constructor which takes the most arguments, prepares the fakes, and returns the desired instance. It can match the arguments that you pass to the constructor by types.
One might ask: “But what if I want to change the behavior of one of the dependencies?”.
There are two ways of doing it:
The first one is faking the type and passing as an argument:
1 |
<span id="lnum1" class="lnum"> 1:</span> Dependency fakeDep = Isolate.Fake.Instance<Dependency>(); |
1 |
<span id="lnum2" class="lnum"> 2:</span> Isolate.WhenCalled(() => fakeDep.Sum(0, 0)).WillReturn(0); |
1 |
<span id="lnum3" class="lnum"> 3:</span> MyType mf = Isolate.Fake.Dependencies<MyType>(fakeDep); |
The second way is to get the Dependency out of your instance:
1 |
<span id="lnum1" class="lnum"> 1:</span> var real = Isolate.Fake.Dependencies<MyType>(); |
1 |
<span id="lnum2" class="lnum"> 2:</span> var fake = Isolate.GetFake<Dependency>(real); |
1 |
<span id="lnum3" class="lnum"> 3:</span> Isolate.WhenCalled( () => fake.Sum(0,0) ).WillReturn(2); |
*NOTE that Isolate.Fake.Dependencies can’t handle ambiguity issues so you have to be explicit.
There is only one thing left and it is the added value of Isolate.Fake.Dependencies<>();
It’s always nice to have our job done for us, but there are other benefits in using this method. The main benefit is that you tests become much more robust. Lets say that tomorrow you’ll decide to change the constructor of your class. If Isolate.Fake.Instance<>() is used to instantiate objects, your tests won’t break.
Back to our example, if we change our Ctor to:
1 |
<span id="lnum1" class="lnum"> 1:</span> <span class="kwrd">public</span> MyType(Dependency inDep , <span class="kwrd">int</span> num) |
1 |
<span id="lnum2" class="lnum"> 2:</span> { |
1 |
<span id="lnum3" class="lnum"> 3:</span> dep = inDep; |
1 |
<span id="lnum4" class="lnum"> 4:</span> } |
1 |
We won’t have to change your test:
1 |
<span id="lnum1" class="lnum"> 1:</span> MyType mt = Isolate.Fake.Dependencies<MyType>(); |