【问题标题】:Moq setup returns reference to objectMoq 设置返回对对象的引用
【发布时间】:2020-08-13 00:11:24
【问题描述】:

假设我有一个名为 MyRequestHandler 的简单类,它有一个名为 ProcessRequest 的方法,该方法简单地接受一个请求对象,将其映射到一个返回对象并返回该对象。 (这显然是我正在研究的更复杂的方法/测试的一个非常简单的示例)。

public class MyRequestHandler
{
  private IMapper _mapper;

  public MyRequestHandler(IMapper maper)
  {
    _mapper = mapper;
  }

  public MyReturnObject ProcessRequest(MyRequestObject requestObject)
  {
    MyReturnObject returnObject = _mapper.Map<MyReturnObject>(requestObject);
    return returnObject;
  }
}

现在进行单元测试(使用 Xunit),我想测试 ProcessRequest 方法,但显然想 Moq Map 方法,如下所示:

MyRequestObject requestObject = new RequestObject()
{
  RequestInt = 1,
  RequestString = "Hello"
};

MyReturnObject returnObject = new MyReturnObject()
{
  MyInt = 1,
  MyString = "Hello"
};

Mock<IMapper> mockMapper = new Mock<IMapper>();
mockMapper.Setup(m => m.Map<MyRequestObject>(requestObject)).Returns(returnObject);

MyRequestHandler requestHandler = new MyRequestHandler(mockMapper.Object);
MyReturnObject response = requestHandler.ProcessRequest(requestObject);

Assert.Equal(returnObject.MyInt, response.MyInt);
Assert.Equal(returnObject.MyString, response.MyString);

这里的问题是 Moq 返回(我猜它应该很明显)对 returnObject 的引用,所以我的断言将始终通过,即使我的方法要在返回对象之前更改值。现在我可以在 Moq Setup/Return 中实例化一个新的 MyReturnObject 并通过我给新的值比较 MyInt 和 MyString,但是如果它是一个非常复杂的对象,有 20 个属性和对象列表呢?也许我想使用 AutoFixture 创建要返回的对象并使用 DeepEqual 来比较它们?这甚至可能吗?我是不是看错了,还是我必须在设置/返回中进行某种类型的克隆才能使其正常工作?

【问题讨论】:

    标签: c# unit-testing moq


    【解决方案1】:

    在这种情况下,您没有收到新数据并且可以验证行为

    在这种情况下,内部状态没有价值

    var requestObject = new RequestObject();
    var returnObject = new MyReturnObject();
    
    ...
    var actual = requestHandler.ProcessRequest(requestObject);
    
    Assert.AreSame(returnObject, actual);
    mockMapper.Verify(
       instance => instance.Map<MyRequestObject>(requestObject),
       Times.Once);
    

    一些细节

    1. 我们不能与其他人共享写入权限,所以我假设你有

      公共类 MyRequestObject { int RequestInt { 得到;私人套装; } 字符串请求字符串 { 获取;私人套装; } }

    否则你总是应该测试参数突变。你可以想象 10 名参与者被深度调用,每个人都应该进行这样的测试。这些测试对变化很弱,它们对新属性没有任何作用。

    1. 最好有良好的编码约定并有时进行代码审查。例如,某人可以从属性中随机删除 private,并且无法通过任何测试捕获。

    2. 例如“write test before of code”等有很多好的实践

    【讨论】:

    • 我认为这个答案根本没有解决这个问题 - 实际上 reference 会是一样的......但问题是如何检测 ProcessRequest 更改 @987654323 @ - 它会通过验证就好了...
    • 我猜我们读到“所以我的断言总是会通过,即使我的方法是在返回对象之前更改一个值”非常不同(这很好)。
    【解决方案2】:

    我不相信有内置功能可以检测被测方法没有更改传递给它的对象。

    选项:

    • 确保返回的对象是不可变的 - 要么让它们一开始就不可变,要么返回不带“set”方法的接口,并使用通过模拟创建的实例
    • 为“预期”和“模拟”值创建单独的实例,然后逐个比较属性。有很多帮助程序库可以做到这一点(我喜欢 FluentAssertions)。
    • 仅对单个属性进行断言,而不是比较对象 - 适用于少量字段。

    如果可能的话,我更喜欢不可变对象 - 防止编写错误代码的可能性,从而减少所需的测试量。

    【讨论】:

    • 谢谢!我在不可变实例和单独实例之间进行辩论。对于单独的实例,我应该能够使用 DeepEqual 库来比较它们。我只需要找到克隆对象的最佳方法。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-06-02
    • 2019-10-13
    相关资源
    最近更新 更多