【问题标题】:How to make Moq ignore arguments that are ref or out如何让 Moq 忽略 ref 或 out 的参数
【发布时间】:2012-06-04 23:38:06
【问题描述】:

在 RhinoMocks 中,您可以将您的模拟作为一揽子声明告诉 IgnoreArguments。在 Moq 中,您似乎必须为每个参数指定 It.IsAny()。但是,这不适用于 ref 和 out 参数。在我需要 Moq 内部服务调用以返回特定结果的情况下,如何测试以下方法:

public void MyMethod() {
    // DoStuff

    IList<SomeObject> errors = new List<SomeObject>();
    var result = _service.DoSomething(ref errors, ref param1, param2);

    // Do more stuff
}

测试方法:

public void TestOfMyMethod() {
    // Setup
    var moqService = new Mock<IMyService>();
    IList<String> errors;
    var model = new MyModel();

    // This returns null, presumably becuase "errors" 
    // here does not refer to the same object as "errors" in MyMethod
    moqService.Setup(t => t.DoSomething(ref errors, ref model, It.IsAny<SomeType>()).
        Returns(new OtherType()));  
}

更新:因此,将错误从“ref”更改为“out”是可行的。所以看起来真正的问题是有一个你不能注入的 ref 参数。

【问题讨论】:

  • 您能否发布DoSomething 的签名,因为在您的示例中它有3 个参数out errors, param1, param2,但在您的测试中,您使用两个参数调用它out errors, It.IsAny&lt;SomeType&gt;() 也许您在模拟错误的重载,因为您的代码应该可以正常工作,请参阅moq help methods section
  • 这只是一个例子 - 但我已经更新了测试以并行示例代码

标签: c# moq


【解决方案1】:

正如您已经发现问题在于您的 ref 参数。

Moq 目前仅支持 ref 参数的精确匹配,这意味着只有当您传递您在 Setup 中使用的相同实例时,调用才会匹配。所以没有通用匹配,所以It.IsAny() 不起作用。

见起订量quickstart

// ref arguments
var instance = new Bar();
// Only matches if the ref argument to the invocation is the same instance
mock.Setup(foo => foo.Submit(ref instance)).Returns(true);

起订量discussion group:

Ref 匹配意味着只有当方法是 用同一个实例调用。 It.IsAny 返回 null,所以可能 不是你要找的。

在设置中使用与实际调用中相同的实例,并且 设置将匹配。

【讨论】:

  • 因此,在存在嵌套调用的情况下,例如 ref/out 变量设置在我试图模拟的调用的方法中,我就被卡住了。对于没有此限制的模拟框架有什么建议吗?
  • 我认为 RhinoMocks IgnoreArguments() 选项应该这样做 - 会尝试。
  • 更新,对于这些情况,我们不得不切换到 RhinoMocks。实现如下所示: IList 错误; _repository.Stub(t => t.MethodName(out errors).OutRef(new List()).IgnoreArguments();
【解决方案2】:

正如@nemesv 之前提到的,It.IsAny 返回 null,因此您不能将其用作 ref 参数。为了使调用起作用,需要将一个实际对象传递给它。

当您无权访问要通过 ref 传递的对象的创建时,就会出现问题。如果您确实可以访问真实对象,则可以简单地在测试中使用它,而完全忘记尝试模拟它。

这是使用提取和覆盖技术的解决方法,可以让您做到这一点。顾名思义,您将有问题的代码提取到它自己的方法中。然后,在从被测类继承的测试类中重写该方法。最后,您设置您的真实对象,将其传递给您新创建的测试类,并根据需要测试您的引用调用。

这是一段很长的(人为的)代码,但它显示了之前和之后,最后通过了一个通过测试。

using System;
using System.Collections.Generic;
using Moq;
using MoqRefProblem;
using NUnit.Framework;

namespace MoqRefProblem
{
    //This class is the one we want to have passed by ref.
    public class FileContext
    {
        public int LinesProcessed { get; set; }
        public decimal AmountProcessed { get; set; }
    }

    public interface IRecordParser
    {
        //The ref parameter below is what's creating the testing problem.
        void ParseLine(decimal amount, ref FileContext context);
    }

    //This is problematic because we don't have a 
    //seam that allows us to set the FileContext.
    public class OriginalFileParser
    {
        private readonly IRecordParser _recordParser;

        public OriginalFileParser(IRecordParser recordParser)
        {
            _recordParser = recordParser;
        }

        public void ParseFile(IEnumerable<decimal> items)
        {
            //This is the problem
            var context = new FileContext();
            ParseItems(items, ref context);
        }

        private void ParseItems(IEnumerable<decimal> items, ref FileContext context)
        {
            foreach (var item in items)
            {
                _recordParser.ParseLine(item, ref context);
            }
        }
    }

    }

    //This class has had the creation of the FileContext extracted into a virtual 
    //method. 
    public class FileParser
    {
        private readonly IRecordParser _recordParser;

        public FileParser(IRecordParser recordParser)
        {
            _recordParser = recordParser;
        }

        public void ParseFile(IEnumerable<decimal> items)
        {
            //Instead of newing up a context, we'll get it from a virtual method 
            //that we'll override in a test class.
            var context = GetFileContext();
            ParseItems(items, ref context);
        }

        //This is our extensibility point
        protected virtual FileContext GetFileContext()
        {
            var context = new FileContext();
            return context;
        }

        private void ParseItems(IEnumerable<decimal> items, ref FileContext context)
        {
            foreach (var item in items)
            {
                _recordParser.ParseLine(item, ref context);
            }
        }
    }

    //Create a test class that inherits from the Class under Test    
    //We will set the FileContext object to the value we want to
    //use.  Then we override the GetContext call in the base class 
    //to return the fileContext object we just set up.
    public class MakeTestableParser : FileParser
    {
        public MakeTestableParser(IRecordParser recordParser)
            : base(recordParser)
        {
        }

        private FileContext _context;

        public void SetFileContext(FileContext context)
        {
            _context = context;
        }

        protected override FileContext GetFileContext()
        {
            if (_context == null)
            {
                throw new Exception("You must set the context before it can be used.");
            }

            return _context;
        }
    }

[TestFixture]
public class WorkingFileParserTest
{
    [Test]
    public void ThisWillWork()
    {
        //Arrange
        var recordParser = new Mock<IRecordParser>();

        //Note that we are an instance of the TestableParser and not the original one.
        var sut = new MakeTestableParser(recordParser.Object);
        var context = new FileContext();
        sut.SetFileContext(context);

        var items = new List<decimal>()
            {
                10.00m,
                11.50m,
                12.25m,
                14.00m
            };

        //Act
        sut.ParseFile(items);

        //Assert
        recordParser.Verify(x => x.ParseLine(It.IsAny<decimal>(), ref context), Times.Exactly(items.Count));
    }
}

【讨论】:

    【解决方案3】:

    回复于:Setting up Moq to ignore a virtual method 我相信在模拟上设置“CallBase = true”会起作用。请参阅快速入门的“自定义模拟行为”部分

    【讨论】:

      猜你喜欢
      • 2023-03-31
      • 1970-01-01
      • 2016-08-07
      • 2011-07-26
      • 2020-12-24
      • 2017-03-25
      • 2012-03-01
      • 1970-01-01
      相关资源
      最近更新 更多