From test spy to Verify() with Moq

Moq is now my favorite unit testing framework for .NET, and a great poster child for the power of the lambda expression support added to C#. If you are not doing unit tests or Test Driven Development you should, and if you already are and have not checked out Moq, you should.

My tests previous to Moq were using NMock, a very handy tool that looks like a lot of other mock frameworks. In order to setup a mock call you would write something similar to this :

[Simple NMock example]
Mockery mocks = new Mockery();
 IWidgetAdapter mockAdapter = mocks.NewMock();

 IList mockWidgets = new List();
 Widget mockWidget = new Widget();
 mockWidget.Name = "Mock Widget";
 mockWidgets.Add(mockWidget);

 Stub.On(mockAdapter).Method("LoadWidgets").WithNoArguments().Will(Return.Value(mockWidgets));
 WidgetManager widgetManager = new WidgetManager(mockAdapter);

The ugliest thing in the expression above for me was the literal string that describes the method name that will be called. All of a sudden my fancy refactoring tools don't quite reach all of my code and things become brittle. Sure you say, but I run these tests all the time! So it is caught right away anyway right? Yeah, but who wants to be searching and replacing these values after every refactor? Just does not feel right.

Here's the Moq equivelent:

[simple Moq Example]
IList mockWidgets = new List();
 Widget mockWidget = new Widget();
 mockWidget.Name = "Mock Widget";
 mockWidgets.Add(mockWidget);

 Mock mockAdapter = new Mock();
 mockAdapter.Setup(cmd => cmd.LoadWidgets(It.IsAny())).Returns(mockWidgets);
 WidgetManager widgetManager = new WidgetManager(mockAdapter.Object);

See that the "LoadWidgets" string disappears, and refactoring code now properly refactors tests right along with it, very very handy. Some find the need to add .Object when referencing the underlying mocked type annoying (on the call to WidgetManager) but personally I find this a small price to pay.

When I first started using Moq a few weeks ago I didn't go much beyond that example. Which speaks to Moq in that it is VERY easy to get started without much effort and more advanced features really don't get in the way of the simple features.

For a while I was able to do a lot of the testing I had in place by Asserting on values I either had access to or were being returned to me. In those cases where the values I needed were being returned to someone else (say a Service for example) I was in the habit of building stub classes (Test Spy in this case) to handle the outgoing data.

So using the generic service as an example, and wanting to observe and Assert that I am sending the correct requests to that service my previous code would have looked something like this:

[Test spy example]
public class AuthenticationSpy : IAuthenticationService
{
 #region Test Helpers
 public IList ReceivedReqeustContexts = new List();
 public AuthenticationResponse ExpectedResponse { get; set; }
 #endregion

 public AuthenticationResponse AuthenticateUser(AuthenticationRequest request)
 {
  return ExpectedResponse; 
 }

 public AuthenticationResponse RenewAuthenticationTicket(RequestContext context)
 {
  this.ReceivedReqeustContexts.Add(context);
  return ExpectedResponse;
 }

}

[TestMethod]
public void RenewExpiredTicketTest()
{
 AuthenticationSpy _authenticationMock = new AuthenticationSpy();
 Mock _respondingMock = new Mock();

 Mock mockServices = new Mock();
 mockServices.Setup(cmd => cmd.GetAuthenticationService(It.IsAny())).Returns(_authenticationMock);
 mockServices.Setup(cmd => cmd.GetRespondingService(It.IsAny())).Returns(_respondingMock.Object);

 ServiceWrapper.Current.ServiceProvider = mockServices.Object;

 _authenticationMock.ExpectedResponse = GetGoodAuthenticationResponse(DateTime.UtcNow.Add(ServiceWrapper.Current.TimerSleepTimeSpan.Subtract(TimeSpan.FromMinutes(1))));

 // initialize will call authenticate() in the service wrapper
 ServiceWrapper.Current.Initialize("testing", "Password1", "http://auth", "http://resp");

 // now setup and call any method to trigger a renew of our now expired authentication ticket
 SetupCreateResponse(Guid.NewGuid());
 SurveyController.StartSurvey(new StartSurveyArgs());

 // confirm renew was actually called
 Assert.IsTrue(_authenticationMock.ReceivedReqeustContexts.Count == 1);
}


This works, and in some cases the control given to you with your test spy can be really helpful, but if I can avoid it I will every time. More classes and more code means more maintenance, even if it is in the test code. So I finally read the docs on the Verify() method on Moq objects and it is awesome. ;-) Here's the same code handled with Moq properly and without the need for a whole new class imitating the authentication service.

[using Verify example]
[TestMethod]
public void RenewExpiredTicketTest()
{
 Mock _authenticationMock = new Mock();
 Mock _respondingMock = new Mock();

 Mock mockServices = new Mock();
 mockServices.Setup(cmd => cmd.GetAuthenticationService(It.IsAny())).Returns(_authenticationMock.Object);
 mockServices.Setup(cmd => cmd.GetRespondingService(It.IsAny())).Returns(_respondingMock.Object);

 ServiceWrapper.Current.ServiceProvider = mockServices.Object;

 _authenticationMock.Setup(cmd => cmd.AuthenticateUser(It.IsAny()))
  .Returns(GetGoodAuthenticationResponse(DateTime.UtcNow.Add(ServiceWrapper.Current.TimerSleepTimeSpan.Subtract(TimeSpan.FromMinutes(1)))));

 // initialize will call authenticate() in the service wrapper
 ServiceWrapper.Current.Initialize("testing", "Password1", "http://auth", "http://resp");
 
 // now setup and call any method to trigger a renew of our now expired authentication ticket
 SetupCreateResponse(Guid.NewGuid());
 SurveyController.StartSurvey(new StartSurveyArgs());

 // confirm renew was actually called
 _authenticationMock.Verify(cmd => cmd.RenewAuthenticationTicket(It.IsAny()), Times.AtLeastOnce());
}

Check out Part 2 in the 4 part series "Beginning Mocking with Moq 3 " gives a short description of how Validate works.

Not bad eh? Again the power of the lambda expression here jumps out at you. Full intellisense and compiler support for describing exactly what you expect that method to receive. The "It" class allows for no description "It.IsAny<t>()" or very precise description as above. The "Times" check also allows you to narrow  Significant savings in code and maintenance and actually using the testing framework as intended (imagine that) ! My only slight annoyance so far is in having to keep count of the number of times a method has been called in order to check that the last piece of code actually resulted in a call and not some code way earlier.

No comments:

Post a Comment