【问题标题】:Moq.Mock<T> setting up expressions into a Mock using MOQ results in mock setups not being matchedMoq.Mock<T> 使用 MOQ 将表达式设置为 Mock 会导致模拟设置不匹配
【发布时间】:2012-01-20 19:34:53
【问题描述】:

我正在尝试模拟一个数据服务上下文,作为其中的一部分,我有一个方法可以接收

  • 一个表达式(谓词),
  • 可选的字符串参数
  • 带有谓词数组的参数。

当我尝试模拟这个方法时,MOQ 总是返回一个

模拟上的所有调用都必须有相应的设置。 TearDown : Moq.MockException : 以下设置不匹配: IContext m => m.Retrieve(It.IsAny() })

下面的接口/实现代码

public interface IContext
{
    IQueryable<T> Retrieve<T>(Expression<Func<T, bool>> predicate,
                                string entitySetName = null,
                                params Expression<Func<T, object>>[] eagerProperties);
}

public class Context : IContext
{
    private readonly DataServiceContext _context;

    public Context(DataServiceContext context)
    {
        this._context = context;
    }

    public IQueryable<T> Retrieve<T>(Expression<Func<T, bool>> predicate,
                                        string entitySetName = null,
                                        params Expression<Func<T, object>>[] eagerProperties)
    {
        DataServiceQuery<T> query = _context.CreateQuery<T>(entitySetName ?? "Default");
        return eagerProperties.Aggregate(query, (current, e) => current.Expand(e.ToString())).Where(predicate);
    }
}

下面是调用上述上下文方法的测试类

public class Test
{
    public string Prop1 { get; set; }
    public string Prop2 { get; set; }
    public string Prop3 { get; set; }
}

public class SomeController
{
    private IContext _context = new Context(
        new DataServiceContext(new Uri("http://whatever/someservice.svc/")));

    public IContext ServiceContext
    {
        get
        {
            return _context ??
                   (_context = new Context(new DataServiceContext(new Uri("http://whatever/someservice.svc/"))));
        }
        set
        {
            _context = value;
        }
    }

    public Test RetrieveSomeInformation()
    {
        IQueryable<Test> tests = _context.Retrieve<Test>
                                                (
                                                    //Param 1
                                                    t => t.Prop1 == "test" && 1 == 1,

                                                    //Param 2
                                                    "Test",

                                                    //Param 3
                                                    t => t.Prop1,
                                                    t => t.Prop2,
                                                    t => t.Prop3
                                                  );
        return tests.First();
    }
}

以下是 MOQ 失败的实际测试,并显示“模拟上的所有调用都必须具有相应的设置”。看不出为什么设置不匹配!任何帮助将不胜感激。

[TestFixture]
public class ControllerTests
{
    public MockRepository Repository { get; set; }
    protected Mock<IContext> MockContext { get; set; }
    public SomeController Controller;

    public List<Test> Tests;
    public Test Test;

    [SetUp]
    public void SetUp()
    {
        Test = new Test { Prop1 = "1", Prop2 = "2", Prop3 = "3" };
        Tests = new List<Test> { Test };

        Repository = new MockRepository(MockBehavior.Strict);

        MockContext = Repository.Create<IContext>();

        Controller = new SomeController { ServiceContext = MockContext.Object };
    }

    [TearDown]
    public void TearDown()
    {
        Repository.VerifyAll();
    }

    [Test]
    public void DetailProgramme_Test()
    {
        MockContext.Setup(m => m.Retrieve<Test>
                            (
                                //Param 1
                                It.IsAny<Expression<Func<Test, bool>>>(),

                                //Param 2
                                It.IsAny<string>(),

                                //Param 3
                                It.IsAny<Expression<Func<Test, object>>>()
                            )
                          ).Returns(Tests.AsQueryable());

        Test info = Controller.RetrieveSomeInformation();


        //myMock.Setup(r => r.Find(It.IsAny<Expression<Func<Person, bool>>>())).Returns(new List<Person>() { new Person() }.AsQueryable());
        Assert.IsTrue(info == Test);
    }
}

【问题讨论】:

    标签: c# unit-testing entity-framework mocking moq


    【解决方案1】:

    使用 Moq 的 It.IsAny&lt;&gt; 而不使用 .CallBack 会强制您编写测试未涵盖的代码。相反,它允许任何查询/表达式通过,从单元测试的角度来看,您的模拟基本上毫无用处。

    解决方案:您要么需要使用回调来测试表达式,要么需要更好地约束您的模拟。无论哪种方式都是混乱和困难的。只要我一直在练习 TDD,我就一直在处理这个问题。我终于拼凑了一个帮助类,以使其更具表现力且不那么混乱。这是一个可能的最终结果:

    mockPeopleRepository
      .Setup(x => x.Find(ThatHas.AnExpressionFor<Person>()
        .ThatMatches(correctPerson)
        .And().ThatDoesNotMatch(deletedPerson)
        .Build()))
      .Returns(_expectedListOfPeople); 
    

    这是讨论它并提供源代码的博客文章:http://awkwardcoder.com/2013/04/24/constraining-mocks-with-expression-arguments/

    【讨论】:

      【解决方案2】:

      从 Mock 派生出一个实例后,您正在设置您的 Mock。一旦你获得了一个对象,这将不会受到模拟的任何更改的影响。示例:

      object instance = mock.Object; // this version wont include what you have configured in the setup
      mock.Setup(...);
      object instance2 = mock.Object;  // get the latest version including whatever you have configured in the setup
      

      修复代码的一种方法是从 Setup 方法中删除实例化语句并使控制器变得惰性,例如:

      public SomeController Controller = new Lazy<SomeController>(() => new SomeController() { ServiceContext = MockContext.Object });
      

      这样,只要您在首次设置后使用控制器,您将始终使用最新版本。

      【讨论】:

        【解决方案3】:

        我相信这取决于您的设置...

        //Param 3
        It.IsAny<Expression<Func<Test, object>>>()
        

        与 params 数组不匹配。试试……

        //Param 3
        It.IsAny<Expression<Func<Test, object>>[]>()
        

        【讨论】:

        • 天啊!!太感谢了!!我不敢相信我忽略了这一点!它现在可以工作了 :-)
        • 是的,没有[],第一个表达式实际上被视为new Expression&lt;Func&lt;Test, object&gt;&gt;[1] { It.IsAny&lt;Expression&lt;Func&lt;Test, object&gt;&gt;&gt;() },即长度为1的新数组。我相信这样的数组通过引用相等匹配,因为它是隐式创建,它永远不会匹配任何东西。
        猜你喜欢
        • 2011-12-20
        • 2011-02-14
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-03-04
        • 2013-07-22
        • 1970-01-01
        • 2014-09-03
        相关资源
        最近更新 更多