Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Return matched invocation from Verify #1117

Closed
Code-Grump opened this issue Dec 11, 2020 · 6 comments
Closed

Return matched invocation from Verify #1117

Code-Grump opened this issue Dec 11, 2020 · 6 comments

Comments

@Code-Grump
Copy link
Contributor

When doing more complex verification, particularly where complex objects are passed to methods, I want to be able to examine the arguments passed to a method in more detail.

Currently I can use the Invocations collection on the mock to find the relevant invocation. In scenarios where multiple invocations have been performed on the mock, this requires doing a search over the collection to find the matching invocation. Since we've already done that work in Verify, it would be very convenient if that method just returned the matching invocation:

var invocation = mock.Verify(m => m.Foo("Bar", It.IsAny<Dictionary<string, string>>()));

var dictionary = (Dictionary<string, string>)invocation.Arguments[1];

// Assert dictionary contains keys and values expected

This would be a "breaking" change to the signature of Verify, but since it currently has a void return, this would not affect most existing code.

@stakx
Copy link
Contributor

stakx commented Dec 11, 2020

There are a few things, apart from this being a breaking binary change, that I don't like about this:

  • (The method should return a collection, not a single invocation.)

  • It breaks the principle of command/query separation. There may be precedents in Moq's API but we shouldn't add more to them if possible.

  • IIRC, verified invocations may belong to a mock other than the one on which Verify is called (with recursive call expressions, I think). It may be slightly confusing to call mock.Verify(...) but get back invocations that don't actually belong to mock.

I recently suggested a helper method invocation.Matches(callExpression) in #1115 (comment) that could possibly work here, too. I'll give you that it wouldn't be quite as convenient (nor performant) as what you're suggesting but that may not be an actual problem in practice.

@Code-Grump
Copy link
Contributor Author

I respect the desire to keep Verify as a pure command.

Most of my assertions follow the structure:

  • The method should have been invoked with one or more key arguments (often literal keys 😊)
  • Now I know the invocation happened, I want to verify the other arguments passed to the method are appropriate.

As an example, assume I am testing some kind of command-processing system. I want to know that a command was executed with a specified target:

mockProcessor.Verify(p => p.ExecuteAsync<FooCommand>(It.Is<FooCommand>(cmd => cmd.Target == "Bar")));

Once I know I have the command I'm looking for, I want to verify the command object itself meets expectations. Currently I have to dig out the matching invocation, repeating myself (making the tests significantly less-maintainable) and then doing some ugly casts to the expected argument types.

Verify has great syntax for identifying the key information I'm looking for and is loose enough to ignore the non-key parameters. The only gap I have is once I know the invocation I expected occurred (identified by the key values being used), how do I verify everything else?

@stakx
Copy link
Contributor

stakx commented Dec 28, 2020

mockProcessor.Verify(p => p.ExecuteAsync<FooCommand>(It.Is<FooCommand>(cmd => cmd.Target == "Bar")));

Once I know I have the command I'm looking for, I want to verify the command object itself meets expectations. Currently I have to dig out the matching invocation, repeating myself (making the tests significantly less-maintainable) and then doing some ugly casts to the expected argument types.

You can use Capture.In together with Verify to "batch-extract" arguments of a specific parameter in a type-safe way:

var commands = new List<FooCommand>();

mockProcessor.Verify(p => p.ExecuteAsync(Capture.In(commands, cmd => cmd.Target == "Bar")));

foreach (var command in commands)
{
    // run additional checks on the captured commands
}

#968 taught me that little trick. 😃

@Code-Grump
Copy link
Contributor Author

I tried this approach and it works pretty well, except my scenarios typically want to deal with exactly one capture, not a collection. However, I can't think of any syntax that would be simpler which is not forbidden by expressions (such as assigning to an out or ref parameter).

@stakx
Copy link
Contributor

stakx commented Jan 6, 2021

@Tragedian you could create some kind of helper around a CaptureMatch<> matcher, say:

class Last<T>
{
    public T Value;

    public T Capture()
    {
        return Moq.Capture.With(new CaptureMatch<T>(arg => Value = arg, arg => true));
    }

    public T Capture(Predicate<T> predicate)
    {
        return Moq.Capture.With(new CaptureMatch<T>(arg => Value = arg, predicate));
    }
}

Then use it as follows:

var lastCommand = new Last<FooCommand>();
mockProcessor.Verify(p => p.ExecuteAsync(lastCommand.Capture()));

I'll leave it up to you to decide whether that's better than capturing into a collection using Capture.In.

@stakx
Copy link
Contributor

stakx commented Jan 6, 2021

Having said that, let us close this issue. I think you've got a reasonably clean way of doing what you need to do without inspecting IInvocations manually, and without us having to change the signature of existing Verify methods. Let me know in case I've still missed something.

@stakx stakx closed this as completed Jan 6, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants