【问题标题】:Mocking a ViewModel for unit testing with Moq?使用 Moq 模拟 ViewModel 进行单元测试?
【发布时间】:2023-03-29 22:46:01
【问题描述】:

单元测试新手。我有一个 WPF 客户端应用程序通过 basicHttpbinding 连接到 WCF 服务。一切都很好。我在我的视图模型中使用简单的构造函数依赖注入,传入一个IServiceChannel,然后我将其称为服务方法,例如:

IMyserviceChannel = MyService;

public MyViewModel(IMyServiceChannel myService)
{
   this.MyService = myService;  
}

Private void GetPerson()
{
  var selectedPerson = MyService.GetSelectedPerson();
}

然后我在客户端应用程序中添加了一个 MS 测试项目,我正在尝试使用 Moq 来模拟我的服务:

  [TestMethod]
    public void GetArticleBody_Test_Valid()
    {
        // Create channel mock
        Mock<IIsesServiceChannel> channelMock = new Mock<IIsesServiceChannel>(MockBehavior.Strict);    

        // setup the mock to expect the Reverse method to be called
        channelMock.Setup(c => c.GetArticleBody(1010000008)).Returns("110,956 bo/d, 1.42 Bcfg/d and 4,900 bc/d. ");

        // create string helper and invoke the Reverse method
        ArticleDataGridViewModel articleDataGridViewModel = new ArticleDataGridViewModel(channelMock.Object);
        string result = channelMock.GetArticleBody(1010000008);
        //Assert.AreEqual("cba", result);

        //verify that the method was called on the mock
        channelMock.Verify(c => c.GetArticleBody(1010000008), Times.Once());
    }

测试在此处的方法调用处以 System.NullReferenceException. Object reference not set to an instance of an object. 失败:

 string result = articleDataGridViewModel.IsesService.GetArticleBody(1010000008);

所以我在徘徊这是否是最好的方法还是我最好以某种方式模拟适用于测试的 viewModel 的孤立部分?

【问题讨论】:

  • “异常被进一步捕获到视图模型中”是什么意思?
  • 抱歉,问题已编辑。我在测试中的方法调用上得到“对象引用未设置为对象的实例”。我刚刚新建了一个 viewModel 对象。显然,对象的所有成员都持有空值,因为我没有从构造函数初始化它们中的任何一个,但不确定为什么会抛出这个异常?
  • 您的视图模型代码**在哪里?
  • 在问题中。我包括了手头的构造函数和方法

标签: c# wpf wcf unit-testing moq


【解决方案1】:

NullReferenceException 被我抛出是因为你使用了MockBehavior.Strict。文档说:

导致此模拟总是为没有相应设置的调用抛出异常。

可能ArticleDataGridViewModel 的构造函数调用了您尚未设置的服务的其他方法。 另一个问题是,您直接调用模拟方法。相反,您应该调用视图模型的方法,该方法调用此方法。

[TestMethod]
public void GetArticleBody_Test_Valid()
{
    // Create channel mock
    Mock<IIsesServiceChannel> channelMock = new Mock<IIsesServiceChannel>();    

    // setup the mock to expect the Reverse method to be called
    channelMock.Setup(c => c.GetArticleBody(1010000008)).Returns("110,956 bo/d, 1.42 Bcfg/d and 4,900 bc/d. ");

    // create string helper and invoke the Reverse method
    ArticleDataGridViewModel articleDataGridViewModel = new ArticleDataGridViewModel(channelMock.Object);
    string result = articleDataGridViewModel.MethodThatCallsService();
    //Assert.AreEqual("cba", result);

    //verify that the method was called on the mock
    channelMock.Verify(c => c.GetArticleBody(1010000008), Times.Once());
}

除此之外,我认为您的方法没有问题。也许视图模型违反了单一职责原则并且做得比它应该做的更多,但这很难根据您的代码示例来判断。

编辑:这是一个完整的示例,说明如何测试这样的内容:

public interface IMyService
{
    int GetData();
}

public class MyViewModel
{
    private readonly IMyService myService;

    public MyViewModel(IMyService myService)
    {
        if (myService == null)
        {
            throw new ArgumentNullException("myService");
        }

        this.myService = myService;
    }

    public string ShowSomething()
    {
        return "Just a test " + this.myService.GetData();
    }
}

class TestClass
{
    [TestMethod]
    public void TestMethod()
    {
        var serviceMock = new Mock<IMyService>();
        var objectUnderTest = new MyViewModel(serviceMock.Object);

        serviceMock.Setup(x => x.GetData()).Returns(42);

        var result = objectUnderTest.ShowSomething();

        Assert.AreEqual("Just a test 42", result);
        serviceMock.Verify(c => c.GetData(), Times.Once());
    }
}

【讨论】:

  • 太好了,谢谢。 viewModel 有一个属性,该属性从 ViewModel 中设置为 null 的方法获取返回值。我编写了一个 ViewModel 构造函数,它只接受一个服务接口并且没有实现任何东西。这只是为了简化单元测试期间的视图模型构造,并且只会在运行测试时受到影响。这是正常程序吗?
  • 通常你不应该添加额外的构造函数来使代码可测试。也许视图模型中还有其他一些问题。 eg.:为什么不是一直注入服务?
  • 服务一直注入。我有另一个构造函数,它接受更多参数并调用其他方法,因此在测试类中更新我的 ViewModel 对象时不想在构造函数值中硬编码。如果我使用这个构造函数,我会得到空异常错误,因为值是在属性设置器等中设置的。
【解决方案2】:

如果无法访问您的视图模型,我们只能为您提供这么多帮助。

但是,这段代码:

Mock<IIsesServiceChannel> channelMock = new Mock<IIsesServiceChannel>(MockBehavior.Strict);
...
ArticleDataGridViewModel articleDataGridViewModel = new ArticleDataGridViewModel(channelMock.Object);
...
string result = articleDataGridViewModel.IsesService.GetArticleBody(1010000008);

不设置您的 IsesService。如果它没有在你的构造函数中设置,这意味着 IsesService 是一个空引用。您不能在空对象上调用方法。

【讨论】:

  • 使用构造函数依赖注入,以便将服务接口对象传递给 viewModel 构造函数。原来是 viewModel 中的一个属性,它被设置为从我正在测试的方法中获取返回字符串。我没有在 ViewModel 构造函数中初始化它,因此在运行时为空。
【解决方案3】:

考虑在更高的抽象级别上模拟,然后与您使用的工具紧密耦合。

也许您的视图模型应该依赖于服务,而不是您使用的工具的详细信息(即 IIsesServiceChannel)。

这是一个例子:

Construct testable business layer logic

【讨论】:

  • 无论谁从我的回答中扣分,至少可以记录下我的建议为什么不好。谢谢。
猜你喜欢
  • 2022-10-13
  • 1970-01-01
  • 2013-07-08
  • 1970-01-01
  • 2019-07-12
  • 2011-12-24
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多