Implementing a testable “Are you sure?” pop-up in MVVM

A few years ago, I blogged about how to implement an “Are you sure” pop-up in MVVM. Well, I’ve grown up (a bit) since then, and realised that the code there wasn’t testable. I therefore take great pleasure in presenting for your delight, a testable “Are you sure” pop-up in MVVM. Aren’t I kind 🙂

Being good boys and girls, we want to write testable code, so if by some miracle we ever get around to writing unit tests, we can run them safe in the knowledge that they stand a fetid dingo’s kidney’s chance of working! One of the main issue involved here is ensuring we keep all view-related code out of the view model.

In particular, it is not uncommon to see calls to MessageBox.Show() in a view model. This is naughty in the extreme, as it would result in a message box popping up when the view model was unit tested. This would rather upset some people…

So, how do we get around this? The answer is surprisingly simple and elegant. Sadly, I can’t claim complete credit for it. I can claim credit for finding it, pulling it together from a couple of disparate blog posts, cleaning it up to make it cleaner and simpler and then writing about it here. I guess I deserve a lollipop for that don’t I?

To illustrate the technique, imagine you have a view model that contains a save command. In order to keep the example simple, we’re going to assume that the view model has a DataChanged bool property, that is true when the data has changed (cunning name eh?). The unit tests would want to check that this property was set correctly when the save button was clicked, based on whether the user clicked OK to save or Cancel not to save. You may not have such a property on your view model, in which case your unit tests would be checking something else, such as a service method being called. This is not hugely complicated, but is not relevant to this example, which is why we are using a simpler use case.

In order to make this work, you need to add the following line of code in your view model…

public Func<string, string, bool> Confirm;

Ideally, this would be in a base view model class, to save you adding the same line of code to every view model you write. This Func will hold the code that is to be executed when you want to ask the user for confirmation (hang on, we’re getting there).

Again, for simplicity, we’re assuming our view model has just one string property called MyName. Whenever MyName is updated, the DataChanged property is set to false. We only enable the Save button if the data has changed (no point in saving if they haven’t changed anything). The SaveCommand code looks like this…

#region SaveCommand
 
 public RelayCommand SaveCommand { get; set; }
 
 private bool SaveCommandCanExecute() {
    return DataChanged;
  }
 private void SaveCommandExecute() {
    if (Confirm != null && Confirm("Are you sure you want to save",
                                     "Confirm save")) {
      // TODO - Call the service logic to save the data
      DataChanged = false;
    }
  }
#endregion

As you can see, we check to see if Confirm is not null, and if so, call it before saving. When we save, we set DataChanged to false.

The code-behind for the view now needs the following line in the constructor…

((MainViewModel)DataContext).Confirm = (msg, capt) =>
  MessageBox.Show(msg, capt,
                  MessageBoxButton.YesNo, MessageBoxImage.Question)
    == MessageBoxResult.Yes;

Again, this really ought to be in a base class for the window/user control to avoid you having to add it to every view.

When you run the code, and click the Save button, you get the pop-up as expected…

If you click Yes, the save is done, and DataChanged is set to false. if you click No, then nothing happens. Well, all sorts of things happen in the depths of the .NET framework, but you wouldn’t want to go there, it’s not a nice place for a clean-living person! The point is that our pop-up confirmation works, and we have kept all UI-related code out of the view model.

Unit testing

So that was the practice, now on to the theory! I realise that this bit is somewhat fanciful, but imagine you wanted to unit test the view model. Go on, humour me! If you had called the message box code from your view model, then running your unit tests would result in a series of message boxes popping up. Not very impressive.

However, with our super-de-duper Grown Up Code(TM), we can write some unit tests easily…

[TestMethod]
public void SaveCommand_ConfirmDoesSave() {
  MainViewModel vm = new MainViewModel {
    Confirm = (_, __) => true,
    MyName = "Jim"
  };
  vm.SaveCommand.Execute(null);
  Assert.AreEqual(false, vm.DataChanged,
    "DataChanged was supposed to be false after saving, but is true");
}
 
[TestMethod]
public void SaveCommand_DenyingDoesNotSave() {
  MainViewModel vm = new MainViewModel {
    Confirm = (_, __) => false,
    MyName = "Jim"
  };
  vm.SaveCommand.Execute(null);
  Assert.AreEqual(true, vm.DataChanged,
   "DataChanged was supposed to be true after rejecting request, but is false");
}

Needless to say, these tests pass…

I know, it would be a bit sad if they failed!

Be First to Comment

Leave a Reply

Your email address will not be published.

This site uses Akismet to reduce spam. Learn how your comment data is processed.