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 ability to set up the .Result of (value) tasks #1126

Merged
merged 6 commits into from
Jan 1, 2021

Conversation

stakx
Copy link
Contributor

@stakx stakx commented Jan 1, 2021

This much more focused version of #1123 only covers setting up the .Result of (value) task-based async methods.

Benefits

  • The dedicated async setup methods .ReturnsAsync, .ThrowsAsync etc. are now largely superfluous:

    -mock.Setup(x => x.GetFooAsync()).ReturnsAsync(foo)
    +mock.Setup(x => x.GetFooAsync().Result).Returns(foo)

    From a user perspective, setups become more uniform in shape. From a maintainer's perspective, we have less work. More specifically, we no longer need to ensure that those async setup verbs are at feature parity with their non-async counterparts.

  • It adds async support where such async verbs aren't currently available:

    -Mock.Of<X>(x => x.GetFooAsync() == Task.FromResult(foo))
    +Mock.Of<X>(x => x.GetFooAsync().Result == foo)

    but also in other places like in Verify…(callExpression, …).

  • It enables recursive setups across async calls in a single setup expression. For example, using just Mock.Of:

    -Mock.Of<X>(x => x.GetFooAsync() == Task.FromResult(Mock.Of<IFoo>(f => f.Bar == bar)))
    +Mock.Of<X>(x => x.GetFooAsync().Result.Bar == bar)

Should .ReturnsAsync, .ThrowsAsync etc. be marked as [Obsolete]?

I am not marking those methods as [Obsolete] just yet, mainly for two reasons:

  • There are a few specialized overloads (e.g. for delayed task completion) that would need to be rewritable using just the regular setup verbs. I'd like to look at those separately.

  • .PassAsync for sequence setups doesn't have an easy replacement, because non-generic tasks do not have a .Result property that one could set up instead. Replacing .PassAsync would require an await-like operator substitute (as proposed in Easier async setups through a new Await(...) operator #1007 and implemented in Await anything & custom awaitable types #1123), which would likely be added together with support for custom awaitable types. Until that happens, it doesn't make sense to deprecate only some (but not all) async setup verbs.

What is still missing?

As noted in the new changelog entry, support in mock.Protected() is still missing, or at least spotty. Also, unlike #1123, there is no support for custom awaitable types just yet; however, most of the necessary machinery is now in place, so this should be relatively easy to add in the future.

Resolves #384, closes #1007, closes #1123.

@stakx stakx added this to the 4.16.0 milestone Jan 1, 2021
@stakx stakx self-assigned this Jan 1, 2021
Comment on lines +300 to +304
Split(memberAccessExpression.Expression, out r, out p);
p.AddResultExpression(
awaitable => Expression.MakeMemberAccess(awaitable, memberAccessExpression.Member),
awaitableFactory);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Once we add support for custom awaitable types, it will be better to preserve the original expression, instead of replacing the .Result access with a new result expression.

Comment on lines +96 to +99
if (this.result is ExceptionResult r)
{
this.result = awaitableFactory.CreateFaulted(r.Exception);
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

There may be circumstances where we may have to call the other CreateFaulted(IEnumerable<Exception> exceptions) overload instead (which, I'm assuming, is meant to cover AggregateException). I haven't yet looked into whether this is actually necessary. Perhaps it would be better to remove that other overload until that time?

Comment on lines +74 to +77
catch (Exception exception)
{
invocation.Exception = exception;
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

We may have to change this some day in order to account for several / aggregate exceptions.

@stakx stakx merged commit f48c0f4 into devlooped:master Jan 1, 2021
@stakx stakx deleted the setup-task-result branch January 1, 2021 12:11
mburumaxwell pushed a commit to faluapp/falu-dotnet that referenced this pull request Jun 12, 2021
Bumps [Moq](https://github.com/moq/moq4) from 4.15.2 to 4.16.0.

#Changelog

*Sourced from [Moq's changelog](https://github.com/moq/moq4/blob/main/CHANGELOG.md).*

> ## 4.16.0 (2021-01-16)
>
> #### Added
>
> * Ability to directly set up the `.Result` of tasks and value tasks, which makes setup expressions more uniform by rendering dedicated async verbs like `.ReturnsAsync`, `.ThrowsAsync`, etc. unnecessary:
>
>    ```diff
>    -mock.Setup(x => x.GetFooAsync()).ReturnsAsync(foo)
>    +mock.Setup(x => x.GetFooAsync().Result).Returns(foo)
>    ```
>
>    This is useful in places where there currently aren't any such async verbs at all:
>
>    ```diff
>    -Mock.Of<X>(x => x.GetFooAsync() == Task.FromResult(foo))
>    +Mock.Of<X>(x => x.GetFooAsync().Result == foo)
>    ```
>
>    This also allows recursive setups / method chaining across async calls inside a single setup expression:
>
>    ```diff
>    -mock.Setup(x => x.GetFooAsync()).ReturnsAsync(Mock.Of<IFoo>(f => f.Bar == bar))
>    +mock.Setup(x => x.GetFooAsync().Result.Bar).Returns(bar)
>    ```
>
>    or, with only `Mock.Of`:
>
>    ```diff
>    -Mock.Of<X>(x => x.GetFooAsync() == Task.FromResult(Mock.Of<IFoo>(f => f.Bar == bar)))
>    +Mock.Of<X>(x => x.GetFooAsync().Result.Bar == bar)
>    ```
>
>    This should work in all principal setup methods (`Mock.Of`, `mock.Setup…`, `mock.Verify…`). Support in `mock.Protected()` and for custom awaitable types may be added in the future. (@stakx, [#1126](devlooped/moq#1126))
>
> #### Changed
>
> * Attempts to mark conditionals setup as verifiable are once again allowed; it turns out that forbidding it (as was done in [#997](devlooped/moq#997) for version 4.14.0) is in fact a regression. (@stakx, [#1121](devlooped/moq#1121))
>
> #### Fixed
>
> * Performance regression: Adding setups to a mock becomes slower with each setup (@CeesKaas, [#1110](devlooped/moq#1110))
>
> * Regression: `mock.Verify[All]` no longer marks invocations as verified if they were matched by conditional setups. (@Lyra2108, [#1114](devlooped/moq#1114))

#Commits

- [`74d5863`](devlooped/moq@74d5863) Update version to 4.16.0
- [`424fe31`](devlooped/moq@424fe31) Fix typo in changelog
- [`f48c0f4`](devlooped/moq@f48c0f4) Merge pull request [#1126](devlooped/moq#1126) from stakx/setup-task-result
- [`6f6a89d`](devlooped/moq@6f6a89d) Update the changelog
- [`66bcb21`](devlooped/moq@66bcb21) Enable `task.Result` in delegate-based setup methods
- [`42521c4`](devlooped/moq@42521c4) Add ability in `IAwaitableFactory` to create result...
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Easier async setups through a new Await(...) operator Async Method Support Clean-up
1 participant