Monday, September 27, 2010

Unit testing, how do I mock a signature with ref or out?

So, recently I had the distinct pleasure of mocking a service with method members that contained ref parameters. I thought this rather odd (I have never been a fan of 'in-place' modifications or returns), but not particularly special. That is, not until I came to unit test consumers of the service.

// immutable third-party service interface
public interface ISubmitMessage
{
    // ugly method
    void Submit (string username, string password, ref byte[] message);
}

// our poor poor consumer that has to interface
// with big bad mean service above
public class SubmitAdapter
{
    void Process (ISubmitMessage service)
    {
         // 1. set username and password from configuration
         string username = string.Empty;
         string password = string.Empty;

         // 2. generate byte-encoding of a string message
         byte[] messageBytes = null;

         // 3. invoke service
         service.Submit (username, password, ref messageBytes);

         // 4. inspect messageBytes return value for
         // success\failure
    }
}

Depending on our mocking solution, ref and out method parameters may or may not be supported. From personal experience many mocking frameworks do not support ref and out parameters. My current mock solution of preference is Moq, and with Moq v4.0.10827 (beta) we cannot verify pass-in and pass-out parameters out-of-box.

[Test]
public void Test_Process ()
{
    Mock<ISubmitMessage> service = 
        new Mock<ISubmitMessage> (MockBehavior.Strict);

    string username = "some.username";
    string password = "some.password";
    byte[] messageIn = null;
    byte[] messageOut = new byte[] { 1 };

    // ideally, we would like something similar to this
    service.
        Setup (
        s => s.Submit (
        username,
        password,

        // where we inspect on way in, and define a return value
        ref It.IsRef<byte[]> (d => Equals (d, null)).Returns (messageOut)));

    SubmitAdapter adapter = new SubmitAdapter ();
    adapter.Process (service.Object);

    service.VerifyAll ();
}

Unfortunately, this feature set does not quite exist yet - though as we shall soon see, we do not necessarily need direct support.

The key to mocking ref and out method parameters is understanding our requirements as a consumer and our requirements as a producer - and while disjoint, they can be satisfied via transform. By example, our consumer expects a functional implementation of a specific interface,

public class SubmitMessageStub : ISubmitMessage
{
    public void Submit (string username, string password, ref byte[] message)
    {
        // ??? 
    }
}

our test method expects a contract we can mock,

// a mock-friendly "normalized" version of original interface
public interface ISubmitMessageMock
{
    // similar signature to source method ISubmitMessage.Submit,
    // note new response object,
    SubmitResponse Submit (string username, string password, byte[] message);

    // new response class, a super-set of data expected to be 
    // returned
    public class SubmitResponse
    {
        // contains return message value
        public byte[] Message { get; set; }
    }
}

Jumping back to our consumer, it is a relatively trivial matter to transform from original to mock interface

public class SubmitMessageStub : ISubmitMessage
{

    // simple constructor that accepts a mock-friendly implementation
    private readonly ISubmitMessageMock _mock = null;
    public SubmitMessageStub (ISubmitMessageMock mock)
    {
        _mock = mock;
    }

    public void Submit (string username, string password, ref byte[] message)
    {
        // NOTE: do NOT add any additional parameter checking
        // or verification. our responsibility is to perform a
        // transform and delegate, let expectation testing occur
        // in caller,

        // 1. transform to mock
        ISubmitMessageMock.SubmitResponse response = _mock.
            Submit (username, password, message);
        // 2. reverse-transform
        message = response.Message;
    }
}

Our test setup now looks like

[TestMethod]
public void Test_Process()
{
    Mock<ISubmitMessageMock> serviceMock =
        new Mock<ISubmitMessageMock> (MockBehavior.Strict);

    // proper setup, you can do this with Moq!
    string username = "some.username";
    string password = "some.password";
    byte[] message = null;

    // we control out parameters here through internal
    // response object
    ISubmitMessageMock.SubmitResponse serviceMockResponse =
        new ISubmitMessageMock.SubmitResponse 
        {
            Message = new byte[] { 1, }, 
        };

    serviceMock.

        // we control in parameters here through standard
        // Moq inspection
        Setup (s => s.Submit (username, password, message)).
        Returns (serviceMockResponse);

    SubmitAdapter adapter = new SubmitAdapter ();

    // simply wrap mock-friendly version with our stub!
    adapter.Process (new SubmitMessageStub (serviceMock.Object));

    // and finally verify service interactions!
    serviceMock.VerifyAll ();
}