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

Add an helper class to simplify parameter capture #251

Merged
merged 1 commit into from
Jun 21, 2016

Conversation

ocoanet
Copy link
Contributor

@ocoanet ocoanet commented Mar 9, 2016

It is frequently required to capture a mock parameter to perform complex assertions. Parameter capture is a regular request from new Moq users.

It can be done with setups and callbacks, but the syntax can be tricky, especially for methods with many parameters.

This helper class simplifies parameter capture:

// Arrange
var capture = new MockCapture<string>();
mock.Setup(x => x.DoSomething(capture.CaptureAny()));

// Act
// ...

// Assert
Assert.Equal("World!", capture.Last);

@ocoanet
Copy link
Contributor Author

ocoanet commented Mar 31, 2016

It was not easy to name the class members, for example the AsParameter method can be simply called Capture. The RunOnCapture and CopyOnCapture methods can also be renamed or changed to delegate properties.

I am open to any suggestion or comment.

@kzu
Copy link
Contributor

kzu commented May 25, 2016

Need to think a bit more about this, but it looks interesting... Maybe others can chime in too for feedback?

@hahn-kev
Copy link
Contributor

This would be really cool, I would like to see matching support with this though, currently it just matches all, while that's the typical use case I've had cases for other captures. You might also want to get the argument for a specific call, so provide index access to the underlying list.

@ocoanet
Copy link
Contributor Author

ocoanet commented Jun 16, 2016

@hahn-kev I updated the PR to include match support. The new syntax is

  • mock.Setup(x => x.DoSomething(capture.CaptureAny())) or
  • mock.Setup(x => x.DoSomething(capture.Capture(p => p.StartsWith("W"))))

/// <summary>
/// Gets all the captured parameter values.
/// </summary>
public IEnumerable<T> All { get { return _capturedValues; } }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd expose IList to make this more flexible, maybe wrapping a ReadOnlyList?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@ocoanet ocoanet force-pushed the master branch 2 times, most recently from a00ddf9 to d96da91 Compare June 16, 2016 16:47
@ocoanet
Copy link
Contributor Author

ocoanet commented Jun 16, 2016

I removed the underscores on the private field names to follow the naming rules of the project, sorry about that.

/// Gets the parameter to use in the setup expression.
/// </summary>
/// <returns>The setup expression parameter</returns>
public T CaptureAny()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think capture.Any() reads better, and since there is already a Linq Enumerable.Any() and an overload Enumerable.Any(func predicate), I'd propose to make this method and Capture both named Any

Copy link
Contributor Author

@ocoanet ocoanet Jun 17, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I choosed the names CaptureAny and Capture to match IsAny and Is methods but I also like your idea to have only one method name with overloads. However, I think Any is confusing because the class exposes captured values through methods like All or Last. Those methods could be renamed but it seems to me that naming the capture method Capture is more straightforward. What do you think ?

@ocoanet
Copy link
Contributor Author

ocoanet commented Jun 21, 2016

Ok, I decided to refactor everything to take your remarks into account.

I removed MockCapture, there are now two new classes:

  • Capture, a static class that can be used to create simple captures.
  • CaptureMatch<T>, a match class that can run a callback on parameter values.

The main use case is now straightforward:

// Arrange
var mock = new Mock<IFoo>();
var items = new List<string>();
mock.Setup(x => x.DoSomething(Capture.In(items)));

// Act
// ...

// Assert
var expectedValues = new List<string> { "Hello!" };
Assert.Equal(expectedValues, items);

Advanced use cases are still supported using CaptureMatch<T>:

var mock = new Mock<IFoo>();
var captureMatch = new CaptureMatch<string>(s => CustomCode(s));
mock.Setup(x => x.DoSomething(Capture.With(captureMatch)));

@kzu kzu merged commit e500f69 into devlooped:master Jun 21, 2016
@kzu
Copy link
Contributor

kzu commented Jun 21, 2016

Beautiful!

Thanks a lot!

@kzu
Copy link
Contributor

kzu commented Jun 21, 2016

btw, are you https://twitter.com/ocoanet?

@kzu
Copy link
Contributor

kzu commented Jun 21, 2016

Shipped! https://www.nuget.org/packages/Moq/4.5.10

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants