【问题标题】:Setup a Mocked (Moq) class that only exposes properties设置仅公开属性的模拟 (Moq) 类
【发布时间】:2015-07-21 15:25:28
【问题描述】:

我正在尝试模拟(使用 Moq)一个类,在一个只公开两个属性的类上设置一个返回对象。

在我有限的起订量经验中,我通常会使用 Setup() lamda 来定义方法调用,然后使用 Returns() 来返回所需的输出。

我在这里遇到的是Setup()。当构造函数完成工作、填充两个属性然后返回时,没有可以调用的“方法”。

我想模拟的班级...显然被愚弄了:

public class CarResponse
{
    public IMetaModel meta { get; set; }
    public List<ICarModel> cars { get; set; }

    public CarResponse(Common.Models.Car car)
    {
        this.cars = new List<ICarModel>();
    }
}

我微弱的嘲讽尝试:

private Mock<CarResponse> _carResponse = new Mock<CarResponse>(MockBehavior.Strict);
_carResponse.Setup( ????? ).Returns(new CarResponse() { meta = new MetaModelV2(), cars = foo });

为了进一步澄清......这是我正在尝试为其编写单元测试的代码:

public HttpResponseMessage AddPickup()
{
     //....code removed for brevity....

    //this repository is mocked and returns the object exactly as I want it
     var car = carRepository.GetCar(carId);

   if (!errorInfo.Any()) //This check is bogus it never gets sets
   {
     RequestHelper rqh = new RequestHelper();

     response = rqh.CreateResponse(Request, HttpStatusCode.OK, new CarResponse(car));
}

我的单元测试:

[TestMethod]
public void AddValidPickupCorrectResponse()
{
   //arrange
   //...lots of code here left off for setting up http context etc

  //act---
  var response = controller.AddPickup();

  //assert

}

如果我按照建议使用预先确定的对象,我将如何将其“挂钩”到被测代码。例如,我编写了一个使用我的预装对象而不是 Moq 的单元测试,但是我如何让 SUT 使用该预装对象?

【问题讨论】:

  • CarResponse 是一个PoCo 类,为什么要模拟它?
  • @OldFox...是的,它确实是一个 PoCo...我如何将它从我的单元测试“传递”到 SUT?
  • 您的 SUT 看起来如何?示例中的问题是什么?(带有CreateResponse 的示例)请添加UT,您要验证的场景以及被测类...
  • 您希望您的AddPickup 方法如何使用模拟对象?它直接调用new CarResponse,那么它怎么知道你想让它做一些不同的事情呢?
  • 我想这就是您要问的问题。您可能需要像 CarResponse 工厂这样的东西,它可以在测试时返回模拟对象。但我认为我们没有足够的细节来知道这是否真的是您在这种情况下想要的。 car 变量来自哪里?你希望你的测试断言什么?

标签: c# unit-testing moq


【解决方案1】:

很少有问题会妨碍对上述代码进行正确的单元测试:

  1. 更新响应助手
  2. 更新 CarResponseObject

本质上,除非真正 POCO 中的类(即只有具有公共 setter 和 getter 的数据),否则使用“new”是单元测试的杀手。 IE。它不是单元测试(单独测试单元/方法)。它测试 CarResponse ctor 的行为,以及 RequestHelper 的工作。

考虑以下更改:

  1. 注入 RequestHelper(这样您就可以模拟 CreateResponse 方法)
  2. 使用并注入一些排序的映射工厂,可以从 Car 创建 CarResponseObjects。
  3. 考虑用 CarResponse 实现 IResponse 之类的东西,这样您的 RequestHelper 或工厂就可以返回接口。

通过以上所有内容,您的测试将如下所示(伪代码,不完整):

//arrange
//....
var carInDB = new Car();
_repoMock.Setup(...).Returns(car);

var carResponse = Mock.Of<IResponse>();
_mapperMock.Setup(m=>m.CreateResponse(car).Returns(carResponse);

var responseFromHelper = new WhateverResponseIsNeeded(); //(or create a new mock IResponse - note! new mock, different than car response
_helperMock.Setup(_controller.Request, HttpStatusCode.OK, carResponse).Returns(responseFromHelper);

//act
var response = _controller.AddPickup();

//assert
response.Should().Be.SameInstanceAs(responseFromHelper)

【讨论】:

  • 所以你已经明智地解释了我的大部分焦虑。我正在使用一个超过一半的圈复杂度为 20+ 的代码库。在我们开始重构之前,我试图获得一个单元测试套件以提供一些覆盖。因此,就目前而言,很多“单元测试”并不是孤立的,但这是重构的目标。清理单元测试和代码库。
【解决方案2】:

您可以使用 SetupGet 和 SetupSet 来模拟属性。但是,我认为您不能模拟具体的类。

如果您正在处理一个值类型,您可能会发现不去模拟并且只使用一个预先固定的对象会更容易。

【讨论】:

  • 从概念上讲,我明白你在说什么,但在我的编辑中,你会发现我缺乏一些知识,无法按照你的建议去做。
猜你喜欢
  • 2012-08-21
  • 2017-07-25
  • 1970-01-01
  • 2010-11-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-10-07
相关资源
最近更新 更多