There are two ways to beat this – constructor moling and AllInstances. First I’m going to talk about constructor moling, then AllInstances, and then I’m going to give a little bit of information as to how this all works. I don’t know about you but sometimes a little bit of understanding about what’s going on behind the scenes really helps me to perceive how to use a particular tool.
Let’s resurrect Example from the last post, but without the possibility for constructor injection.
public class Example { MyDependency m_dependency; public Example() { m_dependency = new MyDependency(10); } public string Execute() { return "Dependency string is: " + m_dependency.MyDependencyMethod(); } } public class MyDependency { int m_dependencyInt; public MyDependency(int myInt) { m_dependencyInt = myInt; } public string MyDependencyMethod() { return "MyInt times 2 = " + m_dependencyInt*2; } }Code like the above is written all the time, and it’s the wrong way to do things (I’m growing as a developer, so I myself didn’t really realize why until recently). Whoever uses Example never gets to say anything about the code it depends on, and if the code it depends on is important enough to be in its own class, that’s probably a bad thing. Maybe in the domain this code is written for, it might make sense, but even then this code can’t be unit tested… unless we bring in Moles!
Since we never get a chance to drop in our own MyDependency or a mole of it, what can we do? One option is to change what “new MyDependency(10)” does. Witness the power:
[TestMethod()] [HostType("Moles")] public void ExecuteTest() { MMyDependency.ConstructorInt32 = (inst, intArg) => { new MMyDependency(inst) { MyDependencyMethod = () => "Moled! Oh yeah, and the int you gave me was " + intArg }; }; Example target = new Example(); string actual = target.Execute(); }Yow, what’s going on here? When you mole a type that can be instantiated, each constructor for the type becomes a static property on the mole type. The type of the property is an Action<>, which you should recognize as a delegate that takes parameters of the types inside the angle brackets and returns nothing. The first type in XYZ is always the type being moled (whaaaaaa….?), and the rest are determined by the parameters the constructor takes. The constructor we mole takes a single parameter of type Int32, so the type MMyDependency.ConstructorInt32 is Action<MyDependency, int>. But… an Action doesn’t return anything, so how could this possibly be a constructor? And what is “inst”? I’ll explain later in this post.
The body of the lambda is essentially how every moled constructor will look: you create an instance of the moled type using the constructor that takes an instance of the type being moled (see how we pass in inst when newing up MMyDependency?), and you mole the methods on that instance by setting the appropriate properties. Constructor moling is the only place you use the mole type’s constructor overload that takes a parameter, and you always use this constructor overload when moling a constructor.
So what’s the other way to control what happens when Example calls MyDependencyMethod? The MMyDependencyMethod has a public static class nested inside of it called AllInstances that, like a mole instances, has properties corresponding to each instance method. The difference between the delegate types of these properties and the delegate types on a mole instance’s properties is that these delegate types have an extra parameter tacked on to the front of the type of the class being moled. You don’t have to do anything with it, but you still have to declare it. Here’s an example of moling MyDependencyMethod on all instances of MyDependency that get created in the scope of this test:
[TestMethod()] [HostType("Moles")] public void ExecuteTest() { MMyDependency.AllInstances.MyDependencyMethod = (inst) => "Moled!"; Example target = new Example(); string actual = target.Execute(); }So now a little bit about what’s going on behind the scenes. I don’t have a whole lot of information on _Detours, the API that sits underneath Moles that actually manages registration of method detours, but I do know that it operates with a few simple method calls that that take in a specific instance and information about the method being detoured. When you generate a mole for a class that can be instantiated, what you get is a MoleBase<T>, where T is the type being moled. This class is actually a temporary “holster” for an instance of type T that gives you access to those _Detours methods via simple properties. When you create a new mole instance from scratch in your test, _Detours uses a neat trick to instantiate a “raw” instance of type T that is completely uninitialized – no constructors are run and even type initializers aren’t run – and “holsters” it. When you set moles on methods via properties on the MoleBase<T>, you are calling through to _Detours to detour methods on that instance. Finally, when you pass in the mole instance to a constructor or whatever else uses it as a dependency, the MoleBase<T> just hands back the nested instance of T (this is done with an override to the “implicit conversion” operator). When your tested code runs, it depends on a completely uninitialized instance of T that’s been registered in _Detours.
This also explains the need for the first parameter of type T that you are required to have when you mole a constructor. If you look closely, the delegate required when you mole a constructor is an Action<>: it doesn’t return anything. What Moles does for you is create a “raw” instance of T, hand it to your delegate so you can use the Moles API to register detours, and then that registered instance is returned to your code under test.
Pretty cool huh?
This ends my deep dive into Moles. I could go deeper but I’m starting to get away from the practical and into “Moles theory.” In reality, any test code trying to do something really fancy with the Moles API in the course of trying to get real, productive work done is probably abusing it – what you should be doing is trying to do the simplest thing that will let you unit test your code in a practical way. Moles is pretty interesting to play around with but I need to move on to more practical topics. I do have a few more posts about Moles in particular – a couple real-world examples from my work including a case where I needed to mole methods on a type as well as one of the type’s ancestors, working around a few tricky bits, the notable absence of moles for a few important types in mscorlib, and how to use “friend assemblies” with moles – but after that I’m moving on to explore Pex in greater detail.
No comments:
Post a Comment