Ilya Chernomordik
Ilya Chernomordik

Reputation: 30205

Using moq to verify a call to a function with param parameters

I have an ILogger interface with LogTrace(string value, params object[] parameters). Now I want to verify that the LogTrace is called and the string to log contains some id. The problem is that it can be called differently. E.g. 1) LogTrace("MyString " + id) 2) LogTrace("MyString {0}", id) and so on.

Is there a good way with Moq to verify all the scenarios? I can only think of creating a hand-made mock that will format the string that will be available for verification.

Upvotes: 17

Views: 12051

Answers (3)

rla4
rla4

Reputation: 1246

I don't think there is an easy way for doing what you need here. The problem is that you need to ensure that a certain combination of values will be passed to your method, which results in many different verifiable scenarios:

  • String contains ID AND parameters contains ID - pass
  • String contains ID AND parameters does not contain ID - pass
  • String does not contain ID AND parameters contains ID = pass
  • String does not contain ID AND parameters does not contain ID - fail

However, Moq does not support this sort of conditional expressions between different arguments of your verifiable method. One possible solution is to check for the absence of an id, instead of its presence in either argument. Try something like:

mock.Verify(m => m.LogTrace(
    It.Is<string>(s => !s.Contains(id)), 
    It.Is<object[]>(o => !o.Contains(id))), Times.Never());

What we are doing here is verifying whether the fail condition is ever met - that is, your string does not contain an id, and neither does your object array. We use Times.Never() to make sure that this situation should never happen.

Keep in mind, however, that the code might not be obvious at first glance; make sure you properly explain your intent once you write it.

Upvotes: 3

Wiktor Zychla
Wiktor Zychla

Reputation: 48230

mock.Verify( m => m.LogTrace( It.IsAny<string>(), It.IsAny<object[]>() ) );

The params object[] is passed to the method as object[] anyway so you just have to match the array somehow (as above for example, this accepts anything).

If you need more control over the list, use the It.Is matcher which allows you to create your own predicate:

 mock.Verify( m => m.LogTrace( It.IsAny<string>(),
            It.Is<object[]>(ps =>
                ps != null &&
                ps.Length == 1 &&
                ps[0] is int &&
                (int)ps[0] == 5
            ) ) );

This example shows how to verify if the param list is not empty and contains 5 as the only parameter of type int.

Upvotes: 20

Grzenio
Grzenio

Reputation: 36649

You could try to use the Callback, but it get fairly convoluted (not tested):

var mock = new Mock<ILogger>();
string trace = null;
mock.Setup(l => l.LogTrace(It.IsAny<string>(), It.IsAny<object[]>()))
        .Callback((s1, par) =>
            {
                trace = string.Format(s1, par);
            });

//rest of the test

Assert.AreEqual(expected, trace);

If ILogger has a small interface, you might consider implementing a stub manually (that would keep all the lines it is supposed to log), and verify that at the end of the test. If you have multiple lines being logged than this will be a more readable setup.

Upvotes: 1

Related Questions