Verifying a specific parameter with Moq

160,123

Solution 1

If the verification logic is non-trivial, it will be messy to write a large lambda method (as your example shows). You could put all the test statements in a separate method, but I don't like to do this because it disrupts the flow of reading the test code.

Another option is to use a callback on the Setup call to store the value that was passed into the mocked method, and then write standard Assert methods to validate it. For example:

// Arrange
MyObject saveObject;
mock.Setup(c => c.Method(It.IsAny<int>(), It.IsAny<MyObject>()))
        .Callback<int, MyObject>((i, obj) => saveObject = obj)
        .Returns("xyzzy");

// Act
// ...

// Assert
// Verify Method was called once only
mock.Verify(c => c.Method(It.IsAny<int>(), It.IsAny<MyObject>()), Times.Once());
// Assert about saveObject
Assert.That(saveObject.TheProperty, Is.EqualTo(2));

Solution 2

I've been verifying calls in the same manner - I believe it is the right way to do it.

mockSomething.Verify(ms => ms.Method(
    It.IsAny<int>(), 
    It.Is<MyObject>(mo => mo.Id == 5 && mo.description == "test")
  ), Times.Once());

If your lambda expression becomes unwieldy, you could create a function that takes MyObject as input and outputs true/false...

mockSomething.Verify(ms => ms.Method(
    It.IsAny<int>(), 
    It.Is<MyObject>(mo => MyObjectFunc(mo))
  ), Times.Once());

private bool MyObjectFunc(MyObject myObject)
{
  return myObject.Id == 5 && myObject.description == "test";
}

Also, be aware of a bug with Mock where the error message states that the method was called multiple times when it wasn't called at all. They might have fixed it by now - but if you see that message you might consider verifying that the method was actually called.

EDIT: Here is an example of calling verify multiple times for those scenarios where you want to verify that you call a function for each object in a list (for example).

foreach (var item in myList)
  mockRepository.Verify(mr => mr.Update(
    It.Is<MyObject>(i => i.Id == item.Id && i.LastUpdated == item.LastUpdated),
    Times.Once());

Same approach for setup...

foreach (var item in myList) {
  var stuff = ... // some result specific to the item
  this.mockRepository
    .Setup(mr => mr.GetStuff(item.itemId))
    .Returns(stuff);
}

So each time GetStuff is called for that itemId, it will return stuff specific to that item. Alternatively, you could use a function that takes itemId as input and returns stuff.

this.mockRepository
    .Setup(mr => mr.GetStuff(It.IsAny<int>()))
    .Returns((int id) => SomeFunctionThatReturnsStuff(id));

One other method I saw on a blog some time back (Phil Haack perhaps?) had setup returning from some kind of dequeue object - each time the function was called it would pull an item from a queue.

Solution 3

A simpler way would be to do:

ObjectA.Verify(
    a => a.Execute(
        It.Is<Params>(p => p.Id == 7)
    )
);

Solution 4

I believe that the problem in the fact that Moq will check for equality. And, since XmlElement does not override Equals, it's implementation will check for reference equality.

Can't you use a custom object, so you can override equals?

Solution 5

Had one of these as well, but the parameter of the action was an interface with no public properties. Ended up using It.Is() with a seperate method and within this method had to do some mocking of the interface

public interface IQuery
{
    IQuery SetSomeFields(string info);
}

void DoSomeQuerying(Action<IQuery> queryThing);

mockedObject.Setup(m => m.DoSomeQuerying(It.Is<Action<IQuery>>(q => MyCheckingMethod(q)));

private bool MyCheckingMethod(Action<IQuery> queryAction)
{
    var mockQuery = new Mock<IQuery>();
    mockQuery.Setup(m => m.SetSomeFields(It.Is<string>(s => s.MeetsSomeCondition())
    queryAction.Invoke(mockQuery.Object);
    mockQuery.Verify(m => m.SetSomeFields(It.Is<string>(s => s.MeetsSomeCondition(), Times.Once)
    return true
}
Share:
160,123

Related videos on Youtube

Luis Mirabal
Author by

Luis Mirabal

Updated on October 03, 2020

Comments

  • Luis Mirabal
    Luis Mirabal over 3 years
    public void SubmitMessagesToQueue_OneMessage_SubmitSuccessfully()
    {
        var messageServiceClientMock = new Mock<IMessageServiceClient>();
        var queueableMessage = CreateSingleQueueableMessage();
        var message = queueableMessage[0];
        var xml = QueueableMessageAsXml(queueableMessage);
        messageServiceClientMock.Setup(proxy => proxy.SubmitMessage(xml)).Verifiable();
        //messageServiceClientMock.Setup(proxy => proxy.SubmitMessage(It.IsAny<XmlElement>())).Verifiable();
    
        var serviceProxyFactoryStub = new Mock<IMessageServiceClientFactory>();
        serviceProxyFactoryStub.Setup(proxyFactory => proxyFactory.CreateProxy()).Returns(essageServiceClientMock.Object);
        var loggerStub = new Mock<ILogger>();
    
        var client = new MessageClient(serviceProxyFactoryStub.Object, loggerStub.Object);
        client.SubmitMessagesToQueue(new List<IMessageRequestDTO> {message});
    
        //messageServiceClientMock.Verify(proxy => proxy.SubmitMessage(xml), Times.Once());
        messageServiceClientMock.Verify();
    }
    

    I'm starting using Moq and struggling a bit. I'm trying to verify that messageServiceClient is receiving the right parameter, which is an XmlElement, but I can't find any way to make it work. It works only when I don't check a particular value.

    Any ideas?

    Partial answer: I've found a way to test that the xml sent to the proxy is correct, but I still don't think it's the right way to do it.

    public void SubmitMessagesToQueue_OneMessage_SubmitSuccessfully()
    {
        var messageServiceClientMock = new Mock<IMessageServiceClient>();
        messageServiceClientMock.Setup(proxy => proxy.SubmitMessage(It.IsAny<XmlElement>())).Verifiable();
        var serviceProxyFactoryStub = new Mock<IMessageServiceClientFactory>();
        serviceProxyFactoryStub.Setup(proxyFactory => proxyFactory.CreateProxy()).Returns(messageServiceClientMock.Object);
        var loggerStub = new Mock<ILogger>();
    
        var client = new MessageClient(serviceProxyFactoryStub.Object, loggerStub.Object);
        var message = CreateMessage();
        client.SubmitMessagesToQueue(new List<IMessageRequestDTO> {message});
    
        messageServiceClientMock.Verify(proxy => proxy.SubmitMessage(It.Is<XmlElement>(xmlElement => XMLDeserializer<QueueableMessage>.Deserialize(xmlElement).Messages.Contains(message))), Times.Once());
    }
    

    By the way, how could I extract the expression from the Verify call?

  • Luis Mirabal
    Luis Mirabal over 13 years
    Thanks, it makes sense to me. What I still can't understand is when to specify details in Setup or Verify. It's quite confusing. At the moment, I'm just allowing anything in Setup and specifying the values in Verify.
  • Luis Mirabal
    Luis Mirabal over 13 years
    How do you think I can check the messages when there are multiple calls? The client take messages and can create multiple queueableMessages, which will end up in multiple calls and in each of those calls, I have to check different messages. I'm still struggling with unit testing in general, I'm not very familiar with it yet.
  • Mayo
    Mayo over 13 years
    I don't think there's a magic silver bullet in terms of how you should do this. It takes practice and you start getting better. For me, I only specify parameters when I have something to compare them to and when I'm not already testing that parameter in another test. As for multiple calls there are several approaches. For setting up and verifying a function that is called multiple times, I usually call setup or verify (Times.Once()) for each call that I expect - often with a for loop. You can use the specific parameters to isolate each call.
  • Mayo
    Mayo over 13 years
    I added some examples for multiple calls - see answer above.
  • Luis Mirabal
    Luis Mirabal over 13 years
    Thanks for your reply about multiple calls, I'll take that approach. What do you think about checking conditions like the answer below? Using asserts seems more natural to me than the lambda expressions in Verify calls, but at the same time a bit tricky.
  • Luis Mirabal
    Luis Mirabal over 13 years
    Yes, I ended up doing that. I realised that the problem was checking the Xml. In the second part of the question I added a possible answer deserialising the xml to an object
  • Rob Church
    Rob Church about 11 years
    One large benefit to this approach is that it will give you specific test failures for how the object incorrect (as you're testing each individually).
  • Will Appleby
    Will Appleby over 8 years
    I thought I was the only one who did this, glad to see it's a reasonable approach!
  • Gianluca Ghettini
    Gianluca Ghettini over 8 years
    "Also, be aware of a bug with Mock where the error message states that the method was called multiple times when it wasn't called at all. They might have fixed it by now - but if you see that message you might consider verifying that the method was actually called." - A bug like this completely invalidates a mocking library IMHO. How ironic they didn't have proper testing code for it :-)
  • stevec
    stevec over 6 years
    I think using It.Is<MyObject>(validator) as per Mayo is better as it avoids the slightly awkward way of saving the parameter value as part of the lambda
  • Anton Tolken
    Anton Tolken almost 5 years
    is this thread safe, for example when running tests in parallel?
  • Rich Tebb
    Rich Tebb almost 5 years
    @AntonTolken I haven't tried it, but in my example, the object that is updated is a local variable (saveObject) so it should be thread safe.
  • Greg Quinn
    Greg Quinn over 4 years
    I can't seem to get this working, I'm trying to verify that my method was called with the Code.WRCC as a parameter. But my test always passes, even though the parameter passed is WRDD.. m.Setup(x => x.CreateSR(Code.WRDD)).ReturnsAsync(0); await ClassUnderTest.CompleteCC(accountKey, It.IsAny<int>(), mockRequest); m.Verify(x => x.CreateSR(It.Is<Code>(p => p == Code.WRCC)), Times.Once());
  • royalTS
    royalTS over 3 years
    @stevec but this approach only guarantees you that a specific call was made, but not if has been called in any other way.
  • bside
    bside about 3 years
    @Greg Quinn doublecheck return type of your TestMethod, it should be async Task, not void.
  • Võ Quang Hòa
    Võ Quang Hòa almost 3 years
    Thanks for your great answer. It's very useful for me