-
-
Notifications
You must be signed in to change notification settings - Fork 839
Description
The discussion below assumes the definition of the following type IX and mock parentMock:
public interface IX
{
IX Child { get; }
Task<IX> GetChildAsync();
string Name { get; }
ValueTask<string> GetNameAsync();
}
var parentMock = new Mock<IX>();Problem:
Today (as of version 4.14.0), Moq has the ability to transparently set up whole object graphs through fluent setup expressions, such as this one:
parentMock.Setup(p => p.Child.Name)
.Returns("Alice");But Moq doesn't offer anything comparable when async methods come into play.
Say you would like to do the same kind of setup, but instead of using the synchronous property .Child you want to use the .GetChildAsync() method. The most succinct solution currently possible is perhaps this:
parentMock.Setup(p => p.GetChildAsync())
.ReturnsAsync(Mock.Of<IX>(c => c.Name == "Alice"));which makes use of ReturnsAsync, which—like other ...Async extension methods—tries to make async method setups a little easier. Without those helpers, the solution would be even more elaborate:
parentMock.Setup(p => p.GetChildAsync())
.Returns(() => Task.FromResult(Mock.Of<IX>(c => c.Name == "Alice")));Even today, we're still trying to make async methods easier to setup (e.g. by adding more ...Async helper methods, or in #384), but the current approaches don't do anything about async support inside fluent setup expressions. Can something be done about that?
Proposed solution:
Say you would like to do the same kind of setup, but instead of using the synchronous property
.Childyou want to use the.GetChildAsync()method.
I think the ideal solution would be this:
parentMock.Setup(async p => (await p.GetChildAsync()).Name)
.Returns("Alice");Unfortunately, the C# compiler does not allow await in LINQ expression trees.
BUT we could compensate this lack of compiler support with a static helper method Await, which would be used as follows:
parentMock.Setup(p => Await(p.GetChildAsync()).Name)
.Returns("Alice");which happens to be even shorter than using native C# keywords.
I strongly suspect that this is feasible, and that it would make all existing ...Async helper methods redundant. Take ReturnsAsync for example:
parentMock.Setup(p => p.GetNameAsync()).ReturnsAsync("Alice");could be rewritten as:
parentMock.Setup(p => Await(p.GetNameAsync())).Returns("Alice");or ThrowsAsync:
parentMock.Setup(p => p.GetChildAsync()).ThrowsAsync(...);could be rewritten as:
parentMock.Setup(p => Await(p.GetChildAsync())).Throws(...);Something that's a little less clear is Callback:
parentMock.Setup(p => Await(p.GetChildAsync())).Callback(...);This setup expression suggests that the callback should only execute on an await parentMock.Object.GetChildAsync(), but not on a simple execution of parentMock.Object.GetChildAsync() (i.e. without the await). That is, Callback may have to be merged with any Returns present; also, a Callback after Returns might have to be transformed to a task continuation (resultTask.ContinueWith(...)). Not sure how that should go.
I'll shortly begin prototyping this. Comments, questions, and suggestions are welcome!