// 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 (); }