The problem
I was writing unit tests for a WPF application using the
MVVM pattern supported by
Caliburn.Micro. I had methods on the view-model that were automatically bound to buttons in the user interface using Caliburn.Micro’s
convention-based bindings. Some of these methods required that a pop-up dialog box be displayed to the end user to confirm an action. The dialog could be submitted or cancelled.
For example, when the user clicked a ‘back’ button to navigate back to a previous screen I needed to check if any data had changed on the current screen. If it had, I needed to present the user with a dialog asking if they really wanted to go back when there was unsaved data. If the user submitted the dialog by clicking OK the application would go to the previous screen.
The code looked something like this:
public void Back()
{
if (!IsChanged)
{
EventAggregator.Publish(new NavigationMessage(GetBackScreen()));
}
else
{
var heading = ResourceProvider.GetString("Dialog.Navigate.Back.Heading");
var text = ResourceProvider.GetString("Dialog.Navigate.Back.Text");
var confirmationDialogViewModel = new ConfirmationDialogViewModel(heading, text);
WindowManager.ShowChromelessDialog(confirmationDialogViewModel);
if (confirmationDialogViewModel.Submitted)
{
EventAggregator.Publish(new NavigationMessage(GetBackScreen()));
}
}
}
By way of explanation, navigation in the application was managed by publishing navigation messages. The
Shell view-model subscribed to the navigation messages and swapped the screens as appropriate when it handled such a message. Messaging was accomplished using Caliburn.Micro’s
EventAggregator. Dialogs were managed using a class derived from Caliburn.Micro’s
WindowManager class.
So the question was, given the code above how could I write unit tests that could test for cases when
_confirmationDialogViewModel.Submitted was either true or false? My intention was to use
Moq to mock the
WindowManager so I would have to be able to access the
_confirmationDialogViewModel passed in to
WindowManager.ShowChromelessDialog(_confirmationDialogViewModel) and to be able to set its
Submitted property.
How could I do that?
The solution
NB: Before I go on I should point out that there is an alternative – and arguably preferable - solution to the one presented below. Rather than instantiating the
ConfirmationDialogViewModel as shown above it should have been injected into the parent view-model as a dependency. By so doing I could have simply set the
Submitted property after instantiating the
ConfirmationDialogViewModel in the unit test. This is what I actually ended up doing but the problem as presented above sparked my curiosity to see if it was possible to get at parameters passed into mocked methods.
Of course you can, by using Moq’s support for callbacks. There are several approaches to this but I liked the following:
[Test]
public void Back_IfConfirmationDialogIsSubmitted_NavigationMessageIsPublished()
{
// Arrange
_viewModel.Data = "Data has changed";
ConfirmationDialogViewModel dialog;
_mockEventAggregator.Setup(x => x.Publish(It.Is<NavigationMessage>(m => m.ScreenToNavigateTo == Screen.Home)));
_mockWindowManager.Setup(w => w.ShowChromelessDialog(It.IsAny<ConfirmationDialogViewModel>()))
.Callback((object rootModel) => ((ConfirmationDialogViewModel)rootModel).Submit());
// Act
_viewModel.Back();
// Assert
_mockEventAggregator.Verify(x => x.Publish(It.Is<NavigationMessage>(m => m.ScreenToNavigateTo == Screen.Home)), Times.Once);
}
In this case we are able to call the
Submit() method on the dialog view-model automatically when the mocked
ShowChromelessDialog method is called (see lines 10 and 11). This in turn caused the
Submitted property to be set to true. Job done!
Incidentally, if you need to you can get a reference to the method parameter by doing something like this:
[Test]
public void Back_IfConfirmationDialogIsSubmitted_NavigationMessageIsPublished()
{
// Arrange
_viewModel.Data = "Data has changed";
_mockEventAggregator.Setup(x => x.Publish(It.Is<NavigationMessage>(m => m.ScreenToNavigateTo == Screens.EnquiryList)));
ConfirmationDialogViewModel dialog = null;
_mockWindowManager.Setup(w => w.ShowChromelessDialog(It.IsAny<ConfirmationDialogViewModel>()))
.Callback((object rootModel) => dialog = (ConfirmationDialogViewModel)rootModel);
// Act
_viewModel.Back();
// We can now get at the dialog variable
var heading = dialog.Heading;
// ... snip ...
}
In the example above you could access the
dialog variable once the mocked
ShowChromelessDialog method has been called.