J-man
J-man

Reputation: 1833

The Following Setups were Not Matched, WPF MVVM Unit Testing with Moq

I am writing a unit test in WPF with Moq to test whether or not a user clicked the "Yes" button on a YesNoCancel MessageBox when they are closing the window. In my CloseCommand I have in my ViewModel gives the following message when the user is closing the window:

var result = _messageSvc.Show(
    "Do you want to save changes?",
    "Save Changes", 
    Services.MessageBoxButton.YesNoCancel,
    Services.MessageBoxIcon.Question, 
    Services.MessageBoxResult.Yes);

My _messageSvc is a private instance inside of a custom Messaging service to display messageboxes. Here is the code for the service:

public interface IMessageSvc
{
    void Show(string message);
    MessageBoxResult Show(string message, string caption, MessageBoxButton buttons, MessageBoxIcon icon, MessageBoxResult defaultResult = 0);
}

public class MessageSvc : IMessageSvc
{
    public void Show(string message)
    {
        MessageBox.Show(message);
    }

    public MessageBoxResult Show(string message, string caption, MessageBoxButton buttons, MessageBoxIcon icon, MessageBoxResult defaultResult)
    {
        return (MessageBoxResult)MessageBox.Show(message, caption, (System.Windows.MessageBoxButton)buttons, (System.Windows.MessageBoxImage)icon, (System.Windows.MessageBoxResult)defaultResult);
    }
}

Note: MessageBoxButton, MessageBoxIcon, and MessageBoxResult are enumerations I made in the same file to mimic what .NET provides out of the box. I am not showing them here for sake of readability

My unit test is:

[TestMethod]
public void ShouldAskToSaveOnCloseRespondYesTest()
{
    // Mock and setup initial user data 
    var u= new User
    {
        UserId = 1,
        UserName = "FirstName LastName",
        FavoriteColor = "Blue"
    };
    mainViewModel.UserInfo= new UserInfoDtoWrapper(u);

    // assert the data has not changed yet
    Assert.IsFalse(mainViewModel.UserInfo.IsChanged);

    // change the user data
    mainViewModel.UserInfo.UserName= "LastName FirstName";

    // assert the data has changed
    Assert.IsTrue(mainViewModel.UserInfo.IsChanged);

    // execute window closing
    mainViewModel.CloseCommand.Execute(new object());

    // verify messagebox shows and the 'Yes' button is clicked
    // this will then save the changes made to the user instance
    var messageBox = new Mock<IMessageSvc>();
    messageBox.Setup(x => x.Show(It.Is<string>(y => y == "Do you want to save changes?"),
        It.Is<string>(y => y == "Save Changes"),
        It.Is<MessageBoxButton>(y => y == MessageBoxButton.YesNoCancel),
        It.Is<MessageBoxIcon>(y => y == MessageBoxIcon.Question),
        It.Is<MessageBoxResult>(y => y == MessageBoxResult.Yes)))
        .Returns(MessageBoxResult.Yes);

    messageBox.Verify();
}

The error comes on the messageBox.Verify() line, but from what I can see I am doing it correctly. I need to get the return value of the messagebox to continue testing what happens after the user clicks the "Yes" button, so I did not want to use Verify with a Linq query inside, instead I need to capture the result.

Thanks

Upvotes: 1

Views: 1533

Answers (2)

Owen Pauling
Owen Pauling

Reputation: 11841

Currently you are setting up your mocked method after testing the code that would call it. Re-arrange your test into the AAA (Arrange, Act, Assert) pattern like so:

var messageBox = new Mock<IMessageSvc>();
messageBox.Setup(x => x.Show(It.Is<string>(y => y == "Do you want to save changes?"),
    It.Is<string>(y => y == "Save Changes"),
    It.Is<MessageBoxButton>(y => y == MessageBoxButton.YesNoCancel),
    It.Is<MessageBoxIcon>(y => y == MessageBoxIcon.Question),
    It.Is<MessageBoxResult>(y => y == MessageBoxResult.Yes)))
    .Returns(MessageBoxResult.Yes);

mainViewModel.CloseCommand.Execute(new object());

messageBox.Verify(x => x.Show(It.Is<string>(y => y == "Do you want to save changes?"),
    It.Is<string>(y => y == "Save Changes"),
    It.Is<MessageBoxButton>(y => y == MessageBoxButton.YesNoCancel),
    It.Is<MessageBoxIcon>(y => y == MessageBoxIcon.Question),
    It.Is<MessageBoxResult>(y => y == MessageBoxResult.Yes)));

Upvotes: 1

dymanoid
dymanoid

Reputation: 15197

This won't work. You have 2 issues in your test code:

  1. You first fire the CloseCommand (which is assumed to call the IMessageSvc.Show method), and only then setup a mock for that service.

  2. You setup a new mock for a service but don't use it.

To correct these errors:

  • Change the order such that it is a correct 'arrange' - 'act' - 'assert' order
  • Inject your mocked service in the view model using dependency injection

Here is an example:

class MainViewModel
{
    private readonly IMessageSvc _messageSvc;

    public MainViewModel(IMessageSvc svc)
    {
        this._messageSvc = svc;
    }
}

[TestMethod]
public void ShouldAskToSaveOnCloseRespondYesTest()
{
    // Arrange
    var messageBox = new Mock<IMessageSvc>();
    messageBox.Setup(x => x.Show(It.Is<string>(y => y == "Do you want to save changes?"),
        It.Is<string>(y => y == "Save Changes"),
        It.Is<MessageBoxButton>(y => y == MessageBoxButton.YesNoCancel),
        It.Is<MessageBoxIcon>(y => y == MessageBoxIcon.Question),
        It.Is<MessageBoxResult>(y => y == MessageBoxResult.Yes)))
        .Returns(MessageBoxResult.Yes)
        .Verifiable();

    var mainViewModel = new MainViewModel(messageBox.Object);

    // Act
    mainViewModel.CloseCommand.Execute(new object());

    // Assert
    messageBox.Verify();
}

Upvotes: 2

Related Questions