【问题标题】:The Following Setups were Not Matched, WPF MVVM Unit Testing with Moq以下设置不匹配,使用 Moq 进行 WPF MVVM 单元测试
【发布时间】:2018-02-03 15:26:19
【问题描述】:

我正在使用 MoqWPF 中编写单元测试,以测试用户在关闭时是否单击了 YesNoCancel MessageBox 上的“是”按钮窗户。在我的CloseCommand 中,当用户关闭窗口时,我的 ViewModel 中会给出以下消息:

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

我的_messageSvc 是自定义消息服务内部的私有实例,用于显示消息框。这是服务的代码:

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);
    }
}

注意:MessageBoxButtonMessageBoxIconMessageBoxResult 是我在同一个文件中创建的枚举,以模仿 .NET 开箱即用的功能。为了便于阅读,我没有在这里展示它们

我的单元测试是:

[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();
}

错误出现在messageBox.Verify() 行,但据我所知,我做得正确。我需要获取消息框的返回值以继续测试用户单击“是”按钮后会发生什么,所以我不想在内部使用带有 Linq 查询的Verify,而是需要捕获结果。

谢谢

【问题讨论】:

  • “错误出现在 messageBox.Verify() 行” - 什么错误?
  • ShouldAskToSaveOnCloseRespondYesTest 抛出异常:Moq.MockVerificationException:以下设置不匹配:IMessageSvc x=> x.Show(It.Is(y => y == "是否要保存更改?"), It.Is(y => y == "保存更改") . . .
  • 设置和验证之间的其余代码在哪里?目前没有进行任何测试。
  • 我想这就是我的问题所在。我需要验证消息框是否显示。然后我应该调用“MessageBox.Show”还是显示我在上面创建的 messageBox 对象?

标签: c# wpf unit-testing mvvm moq


【解决方案1】:

这行不通。您的测试代码中有 2 个问题:

  1. 您首先触发 CloseCommand(假定调用 IMessageSvc.Show 方法),然后才为该服务设置模拟。

  2. 您为服务设置了一个新的模拟,但没有使用它。

要纠正这些错误:

  • 更改顺序,使其成为正确的“排列”-“行为”-“断言”顺序
  • 使用依赖注入在视图模型中注入您的模拟服务

这是一个例子:

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();
}

【讨论】:

  • 我在单元测试类初始化时将消息服务对象添加到视图模型构造函数,以便从那里注入。然而,在单元测试方法中,在按照您显示的方式安排我的代码后,我仍然遇到同样的错误。
  • @J-man,不,你没有按照我的建议安排你的代码。您正在测试方法中为您的服务创建一个新的模拟。必须将这个模拟服务提供给视图模型。您必须在测试方法中创建您的视图模型(正如我建议的那样),或者在创建视图模型的类初始化中设置您的服务模拟。
  • 你是对的@dymanoid,当我已经可以访问在单元测试的初始化方法中注入的类作用域 messageSvc 对象时,我错误地设置了我的消息服务的新模拟实例班级。所以我应该设置messageSvc.Setup(...),而不是我正在做的var messageBox = new Mock&lt;IMessageSvc&gt;(); messageBox.Setup(...)。难怪它找不到正确的设置,你是正确的,需要将 SAME 对象注入 VM,我错误地试图注入一个新对象。谢谢!!我会将您的答案标记为答案。
【解决方案2】:

目前您正在设置您的模拟方法测试将调用它的代码。像这样将您的测试重新安排到AAA (Arrange, Act, Assert) 模式中:

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)));

【讨论】:

  • 我之前试过这个,在这种情况下返回的错误是Expected Invocation on the Mock once, but was 0 timesmessageBox.Verify(x =&gt; x.Show( It.Is&lt;string&gt;(y =&gt; y == "Do you want to save changes?"), It.Is&lt;string&gt;(y =&gt; y == "Save Changes"), It.Is&lt;MessageBoxButton&gt;(y =&gt; y == MessageBoxButton.YesNoCancel), It.Is&lt;MessageBoxIcon&gt;(y =&gt; y == MessageBoxIcon.Question), It.Is&lt;MessageBoxResult&gt;(y =&gt; y == MessageBoxResult.Yes)), Times.Once);
  • 我将 IMessageService 注入 ViewModel 并在单元测试类中初始化,所以当我运行 _messageSvc.Verify(x =&gt; x.Show(It.Is&lt;string&gt;(y =&gt; y == "Do you want to save changes?"), It.Is&lt;string&gt;(y =&gt; y == "Save Changes"), It.Is&lt;MessageBoxButton&gt;(y =&gt; y == MessageBoxButton.YesNoCancel), It.Is&lt;MessageBoxIcon&gt;(y =&gt; y == MessageBoxIcon.Question), It.Is&lt;MessageBoxResult&gt;(y =&gt; y == MessageBoxResult.Yes)), Times.Once); 时它可以工作,但我需要设置消息框以获取返回值
猜你喜欢
  • 2011-12-24
  • 1970-01-01
  • 1970-01-01
  • 2014-01-02
  • 1970-01-01
  • 2018-03-29
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多