Tuesday, April 6, 2010

Moling Static Methods

Arguably, the simplest types of moles are those on static methods. They’re nice and easy because you don’t have to worry about handling behaviors and implementations on specific instances, and the way things work is pretty cut-and-dried: calls to methods that you mole will use your overriding implementation, and calls to methods that you don’t mole will call the real implementation unless you give it something else to do. Here’s my contrived example for this post (I promise I’ll start using more real world examples soon, but for now, it’s easier to demonstrate the behavior of moles this way):
namespace MyProjectToTest
{
    public static class MyStaticClass
    {
        public static int MyStaticAdder(int a, int b)
        {
            return a + b;
        }

        public static int MyStaticSubtractor(int a, int b)
        {
            return a - b;
        }
    }

    public class ClassToTest
    {
        public int MethodToTest(int a, int b)
        {
            return MyStaticClass.MyStaticAdder(a, b) * MyStaticClass.MyStaticSubtractor(a, b);
        }
    }
}
If I set up a test project for this assembly and mole it, here’s what IntelliSense shows me in the Moles namespace:

We got a mole for the static class and a mole and a stub for the other one. This is in line with the rules I posted previously – our static class can’t be instantiated, so it only gets a mole. ClassToTest is a non-abstract, non-sealed class, so it gets a stub and a mole. Let’s look at MMyStaticClass:

Let’s ignore the methods and the Behavior property for the moment. On our mole type, we have static properties named after static methods on MyStaticClass. If you look at the types of the properties, they are both Func delegates, which match the signature of the methods being moled. If you assign delegates to these properties, your delegates will be run instead of the real implementation whenever the method is called. That right there is the magic of moles.

Let’s take a look at an example of how to do this:
[TestMethod()]
[HostType("Moles")]
public void MyTest()
{
    ClassToTest instance = new ClassToTest();
    int result = instance.MethodToTest(8, 4);  // (8+4)*(8-4) = 48
    MMyStaticClass.MyStaticAdderInt32Int32 = (a, b) => a / b; //overriding the static method
    result = instance.MethodToTest(8, 4); // (8/4)*(8-4) = 8. Note we didn't mole MyStaticSubtractor.
}
In the test, we first run MethodToTest, and we can see if we look in the debugger that it returns the expected result, 48. Next, we detour the MyStaticAdder method to perform a division instead. All we have to do is change that static property on the MMyStaticClass type, and the method is detoured – we don’t “new” anything, and by using lambda expressions, our code is short and neat and we don’t even have to do any work outside of the test method! The next calculation makes use of our detour instead of the real implementation. Try running it through the debugger to see what happens. This is a pretty contrived example, but start thinking in terms of classes and dependencies: if you are testing a class that has an ugly, not-abstracted-away hard dependency on a static method you don’t want called during your tests, like File.ReadAllText or Directory.Exists, you can just mole it away (there is a little caveat about moling File and some other BCL methods that I’ll talk about later, but compiler errors will show you what to do if you run into it).

At this point, you may be thinking, “man, I certainly have to know a lot about the source code I’m working with to know what needs to be moled/stubbed/etc.” You would be correct – if you look at the Pex homepage, you’ll see the term “white box testing” right there in the title. White-box testing implies that the implementation under test is fully visible to you, and you just need to verify that it works correctly.

There is a technique you can use, suggested by the documentation, to help you find methods that need to be moled – get all the unmoled methods on a type to throw exceptions if you try to call them! Moles and stubs all have a Behavior property that you can set that will determine what happens when you call methods that you didn’t mole.

If you look in the Microsoft.Moles.Framework.MoleBehaviors class, there are a few properties that return instances of IMoleBehavior. The useful ones are Fallthrough, DefaultValue (“default” here refers to the functionality – this is not the default behavior) and NotImplemented. Fallthrough is the behavior we’ve seen so far – call the real implementation if no mole has been set. In the example above, we didn’t mole MyStaticSubtractor, so the real method still got called. Note that FallThrough is only the default for static methods; I will discuss instance methods in a later post. DefaultValue replaces not-explicitly-moled methods with a stub that simply returns the default value of the return type – it’s a quick way to say “I don’t care what any of these methods that I’m not writing replacements for do, but I don’t want them to throw exceptions and I don’t want them to call the real methods”. NotImplemented will cause a MoleNotImplementedException to be thrown if a method is called on the class that isn’t explicitly moled (this is the default for instance methods).

This last behavior is great for safely figuring out what needs to be moled in order to make your test successful. Usage of it is expected to be so common that there’s a shortcut for setting it as the behavior a mole: MMyMoledclass.BehaveAsNotImplemented(). Some of the official moles documentation suggests doing this for all of your moles and running your test method repeatedly, moling each method that throws an exception, until your test stops throwing exceptions. This way, you can make sure you’ve replaced all the functionality you need to for a given test.
Next up: stubs!

No comments: