简体   繁体   中英

Moq.Mock<T> setting up expressions into a Mock using MOQ results in mock setups not being matched

I am trying to mock out a data service context, as part of this I have a method which takes in

  • an expression (predicate),
  • an optional string parameter
  • a params with an array of predicates.

when I try to mock this method out, MOQ always returns a

All invocations on the mock must have a corresponding setup. TearDown : Moq.MockException : The following setups were not matched: IContext m => m.Retrieve(It.IsAny() })

code below of the interface/implementation

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);
    }
}

The below is a test class that calls the above context method

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();
    }
}

Below is the actual test that MOQ fails with a "All invocations on the mock must have a corresponding setup." Can't see why on earth the setup wont be matched! any help would be appreciated.

[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);
    }
}

I believe it is down to this in your Setup...

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

Which does not match the params array. Try...

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

Using Moq's It.IsAny<> without a .CallBack forces you to write code that's not covered by your test. Instead, it allows any query/expression at all to pass through, rendering your mock basically useless from a unit testing perspective.

The solution: You either need to use a Callback to test the expression OR you need to constrain your mock better. Either way is messy and difficult. I've dealt with this issue for as long as I've been practicing TDD. I finally threw together a helper class to make this a lot more expressive and less messy. Here's one possible end-result:

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

Here's the blog article that talks about it and gives the source code: http://awkwardcoder.com/2013/04/24/constraining-mocks-with-expression-arguments/

You are setting up your Mock after you have derived an instance out of it. Once you gained an object, this will not be affected by any changes to the mock. example:

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

a dity way to fix your code is to remove the instantiate statement from the Setup method and make your Controller lazy, like:

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

This way, as long as you consume your controller after setup for the first time, you will always work with the latest version.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM