2

I am currently examining Moles from the outside while I wait for my VS 2010 license, and I wonder whether Moles allows me to:

  1. provide the ability to assígn multiple mole delegates for a method being moled, perhaps at a test fixture setup level?
  2. switch in runtime in my test case, which of my mole delegates must be invoked for the upcoming call(s) to the moled method being isolated?

Any hints?

Chai
  • 468
  • 4
  • 5

1 Answers1

1

Best Answer:

It is much easier and makes far more sense to include gating logic in the detour method, than using two stubs for the same method! For example, MyMethod reads data from three different files on disk, each requiring different mock data to be returned. We may detour System.IO.File.OpenRead and gate the return value by analyzing the input parameters of OpenRead:

TEST METHOD:

[TestMethod]
[HostType("Moles")]
public void Test()
{
    System.IO.Moles.MFile.OpenReadString = filePath => {
        var mockStream = new System.IO.FileStream();
        byte[] buffer;
        switch (filePath)
        {
            case @"C:\DataFile.dat":
                mockStream.Write(buffer, 0, 0); // Populate stream
                break;
            case @"C:\TextFile.txt":
                mockStream.Write(buffer, 0, 0); // Populate stream
                break;
            case @"C:\LogFile.log":
                mockStream.Write(buffer, 0, 0); // Populate stream
                break;
        }
        return mockStream;
    };


    var target = new MyClass();
    target.MyMethod();
}

TARGET TYPE:

using System.IO;
public class MyClass
{
    public void MyMethod()
    {
        var fileAData = File.OpenRead(@"C:\DataFile.dat");
        var fileBData = File.OpenRead(@"C:\TextFile.txt");
        var fileCData = File.OpenRead(@"C:\LogFile.log");
    }
}

Direct Answer to Your Questions:

Yes to #1: instantiate one type for each detour, and then use each for the desired behavior. And, yes to #2: act upon one instance of the mole type or the other. This requires addition of method input parameters or class constructor injection.

For example, MyMethod reads three data files from disk, and you need to pass back three different data mocks. MyMethod requires three parameters, an overtly intrusive solution. (Note input parameters are FileInfo type; because, System.IO>File is static and can not be instantiated: For example:

TEST METHOD:

[TestMethod]
[HostType("Moles")]
public void Test()
{
    var fileInfoMoleA = new System.IO.Moles.MFileInfo();
    fileInfoMoleA.OpenRead = () => { return new FileStream(); };

    var fileInfoMoleB = new System.IO.Moles.MFileInfo();
    fileInfoMoleB.OpenRead = () => { return new FileStream(); };

    var fileInfoMoleC = new System.IO.Moles.MFileInfo();
    fileInfoMoleC.OpenRead = () => { return new FileStream(); };

    var target = new MyClass();
    target.MyMethod(fileInfoMoleA, fileInfoMoleB, fileInfoMoleC);
}

TARGET TYPE:

using System.IO;
public class MyClass
{
    // Input parameters are FileInfo type; because, System.IO.File
    // is a static class, and can not be instantiated.
    public void MyMethod(FileInfo fileInfoA, FileInfo fileInfoB, FileInfo fileInfoC)
    {
        var fileAData = fileInfoA.OpenRead();
        var fileBData = fileInfoB.OpenRead();
        var fileCData = fileInfoC.OpenRead();
    }
}

UPDATE:

In response to @Chai comment, it is possible to create common methods, within the test project, that may be referenced as the mole detour delegate. For example, you may wish to write a common method that may be referenced by any unit test, that sets up a variety of pre-configured scenarios. The following example displays how a parameterized method could be used. Get creative -- they're just method calls!

TARGET TYPES:

namespace PexMoleDemo
{
    public class MyClass
    {
        private MyMath _math;
        public MyClass()
        {
            _math = new MyMath() { left = 1m, right = 2m };
        }

        public decimal GetResults()
        {
            return _math.Divide();
        }
    }

    public class MyOtherClass
    {
        private MyMath _math;
        public MyOtherClass()
        {
            _math = new MyMath() { left = 100m, right = 200m };
        }

        public decimal Divide()
        {
            return _math.Divide();
        }
    }

    public class MyMath
    {
        public decimal left { get; set; }
        public decimal right { get; set; }

        public decimal Divide()
        {
            return left / right;
        }
    }
}

TEST METHODS: ArrangeScenarios() sets up mole detours, by switching on the enumeration parameter. This allows the same scenarios to be erected, in a DRY manner, throughout many tests.

using System;
using Microsoft.Moles.Framework;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using PexMoleDemo;
[assembly: MoledAssembly("PexMoleDemo")]

namespace TestProject1
{
    [TestClass()]
    public class ProgramTest
    {
        public enum Scenarios
        {
            DivideByZero,
            MultiplyInsteadOfDivide
        }

        private void ArrangeScenario(Scenarios scenario)
        {
            switch (scenario)
            {
                case Scenarios.DivideByZero:
                    PexMoleDemo.Moles.MMyMath.AllInstances.rightGet = 
                        instance => { return 0m; };
                    break;
                case Scenarios.MultiplyInsteadOfDivide:
                    PexMoleDemo.Moles.MMyMath.AllInstances.Divide = 
                        instance => { return instance.left * instance.right; };
                    break;
                default:
                    throw new NotImplementedException("Invalid scenario.");
            }
        }

        [TestMethod]
        [HostType("Moles")]
        [ExpectedException(typeof(DivideByZeroException))]
        public void Test1()
        {
            ArrangeScenario(Scenarios.DivideByZero);
            var target = new PexMoleDemo.MyClass();

            var math = new PexMoleDemo.MyMath() { left = 1, right = 2 };
            var left = math.left;
            var right = math.right;


            var actual = target.GetResults();
        }

        [TestMethod]
        [HostType("Moles")]
        public void Test2()
        {
            ArrangeScenario(Scenarios.MultiplyInsteadOfDivide);
            // Perform some sort of test that determines if code breaks
            // when values are multiplied instead of divided.
        }

        [TestMethod]
        [HostType("Moles")]
        [ExpectedException(typeof(DivideByZeroException))]
        public void Test3()
        {
            ArrangeScenario(Scenarios.DivideByZero);
            var target = new PexMoleDemo.MyOtherClass();

            var math = new PexMoleDemo.MyMath() { left = 1, right = 2 };
            var left = math.left;
            var right = math.right;


            var actual = target.Divide();
        }

        [TestMethod]
        [HostType("Moles")]
        public void Test4()
        {
            ArrangeScenario(Scenarios.MultiplyInsteadOfDivide);
            // Perform some sort of test that determines if code breaks
            // when values are multiplied instead of divided.
        }
    }
}
Mike Christian
  • 1,526
  • 1
  • 15
  • 27
  • My thoughts in more detail: One could use the approach of packing conditional logic into the delegate as a quick solution. However, I still feel having multiple optional delegates for the same moled method are useful in avoiding code duplication in the mole delegate logic. This way, for example, common exception scenarios can be simulated by packing the exception raising code into a separate method that can then be assigned as an alternative delegate to multiple target calls. This way, the exception raising code is maintained in one place and this avoids code clutter. – Chai Aug 08 '11 at 20:55
  • I see that you mean. You may create one or more methods in the test project, that throw exceptions, etc. So long as the method can be referenced by pointer, the mole type can use it as a detour. The delegated methods may have input parameters, to help automate what exception it throws, etc., as you normally would with any method call. I will append my answer with a demonstration of this. – Mike Christian Aug 08 '11 at 23:46
  • The "UPDATE" section has been appended to my answer. – Mike Christian Aug 09 '11 at 00:50
  • Mike, thanks, I could go further with the example and come up with generic methods that could be used as mole delegates. To illustrate: I added a new scenario to throw an exception and assigned a generic method that could be used to throw an InvalidOperationException: ` case Scenarios.ThrowException: PexMoleDemo.Moles.MMyMath.AllInstances.Divide = ThrowInvalidOperationException; break; ` where ThrowInvalidOperationException looks like this: ` private static decimal ThrowInvalidOperationException(T instance) { throw new ...; } – Chai Aug 10 '11 at 06:50
  • Looking back at my ArrangeScenario() method, I should have used the "flag" modifier on the Scenarios enum, and altered the logic to allow arrencing multiple scenarios in a single call. Meh... – Mike Christian Aug 11 '11 at 17:11