My current project is using Moq as our test mocking framework. Although I've been a fan of RhinoMocks for several years I've found that in general, they both support the same features but I'm starting to think I like Moq's syntax better. I'm not going to go into an in-depth comparison of the mocking frameworks, as that's well documented elsewhere. Instead, I want to zero in on a feature that I've had my eye on for sometime now.
Both RhinoMocks and Moq have support for a peculiar feature that let's you invoke a callback method in your test code when a method on your mock is called. This feature seems odd to me because in the majority of cases a mock is either going to return a value or throw an exception - surely invoking some arbitrary method in your test must represent a very small niche requirement.
According to the RhinoMock documentation, the callback takes a delegate that returns true or false. From this it's clear that you would use this delegate to inspect the method's incoming parameters and return false if it didn't meet your expectations. However, as Ayende eludes, this is a powerful feature that can be easily be abused.
Moq provides this feature too, but the syntax is different. Rather than requiring a delegate Func<bool>, Moq's Callback mimics the signature of the mocked method. From this syntax it's not obvious that the callback should be used for validating inbound parameters, which suggests that it could be abused, but it also implies freedom for the test author to do other things. Granted this can get out of control and can be abusive, but perhaps a level of discretion about it's usage is also implied?
Here are a few examples where Callbacks have been helpful:
Validating inbound method parameters
The best example I can imagine for inspecting an inbound parameter is for a method that has no return value. For example, sending an object to a logger. If we were to assume that validation logic of the inbound parameter is the responsibility of the logger, we would only identify invalid arguments at runtime. Using the callback technique, we can write a test to enforce that the object being logged meets minimum validation criteria.
[TestMethod] public void Ensure_Exception_Is_Logged_Properly() { Exception exception; Mock.Get(Logger) .Setup( l => l.Error(It.IsAny<Exception>()) .Callback<Exception>( ex => exception = ex ) Subject.DoSomethingThatLogsException(); Assert.AreEqual(ex.Message, "Fatal error doing something"); }
Changing state of inbound parameters
Imagine a WPF that uses the MVVM pattern and we need to launch a view model as a modal dialog. The user can make changes to the view model in the dialog and click ok, or they can click cancel. If the user clicks ok, the view model state needs to reflect their changes. However if they click cancel, any changes made need to be discarded.
Here's the code:
public class MyViewModel : ViewModel { /* snip */ public virtual bool Show() { var clone = this.Clone(); var popup = new PopupWindow(); popup.DataContext = clone; if (_popupService.ShowModal(popup)) { CopyStateFrom(clone); return true; } return false; } }
Assuming that the popup service is a mock object that returns true when the Ok button is clicked, how do I test that the contents of the popup dialog are copied back into the subject? How do I guarantee that changes aren't applied if the user clicks cancel?
The challenges with the above code is that the clone is a copy of my subject. I have no interception means to mock this object unless I introduce a mock ObjectCloner into the subject (that is ridiculous btw). In addition to this, the changes to the view model happen while the dialog is shown.
While the test looks unnatural, Callbacks fit this scenario really well.
[TestMethod] public void When_User_Clicks_Ok_Ensure_Changes_Are_Applied() { Mock.Get(PopupService) .Setup(p => p.ShowModal(It.IsAny<PopupWindow>()) .Callback<PopupWindow>( ChangeViewModel ) .Returns(true); var vm = new MyViewModel(PopupService) { MyProperty = "Unchanged" }; vm.Show(); Assert.AreEqual("Changed", vm.MyProperty); } private void ChangeViewModel(PopupWindow window) { var viewModel = window.DataContext as MyViewModel; viewModel.MyProperty = "Changed"; }
The key distinction here is that changes that occur to the popup are in no way related to the implementation of the popup service. The changes in state are a side-effect of the object passing through the mock. We could have rolled our own mock to simulate this behavior, but Callbacks make this unnecessary.
Conclusion
All in all, Callbacks are an interesting feature that allow us to write sophisticated functionality for our mock implementations. They provide a convenient interception point for parameters that would normally be difficult to get under the test microscope.
How are you using callbacks? What scenarios have you found where callbacks were necessary?