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

Support mocking the return value when the type is Task/Task<T> #171

Closed
JasonMing opened this issue May 14, 2015 · 5 comments
Closed

Support mocking the return value when the type is Task/Task<T> #171

JasonMing opened this issue May 14, 2015 · 5 comments
Assignees
Milestone

Comments

@JasonMing
Copy link

When Mock<T>.DefaultValue == DefaultValue.Empty, the default return value of async method, which returns Task or Task<T>, will directly return null, then await on them will cause NullRefereceException.
In this case, the "Empty" value should be Task.FromResult(default(T)).

When Mock<T>.DefaultValue == DefaultValue.Mock the default return value of async method will change to the mocking Task/Task<T>, however, the Task/Task<T> are not able to be mocked, then the System.ArgumentExceptionType to mock must be an interface or an abstract or non-sealed class. will be thrown.
In this case, the "Mock" value should be Task.FromResult(Mock.Of<T>()).

@stakx
Copy link
Contributor

stakx commented Jun 11, 2017

When Mock<T>.DefaultValue == DefaultValue.Empty [...], the "Empty" value should be Task.FromResult(default(T)).

This is already implemented.

When Mock<T>.DefaultValue == DefaultValue.Mock [...], the "Mock" value should be Task.FromResult(Mock.Of<T>()).

Agreed. I'm looking at this now.

however, the Task/Task<T> are not able to be mocked, then the System.ArgumentExceptionType to mock must be an interface or an abstract or non-sealed class. will be thrown.

Somehow this sentence became a little garbled. I suppose you meant, "The type to mock must be an interface or an abstract or non-sealed class. If it is not able to be mocked, a System.ArgumentException will be thrown."?

In order to stay true to how mock default values have been produced up until now, mocking a Task<T> for DefaultValue == DefaultValue.Mock would have to appear to work as follows:

  1. Let the empty default value provider produce a completed Task<T> containing default(T).
  2. If that contained default value is not null, return the produced Task<T>.
  3. Otherwise, check if T is mockable. If so, produce another completed Task<T> containing a new Mock<T>() and return it.
  4. Otherwise (if T is not mockable), return the empty completed Task<T> produced in (1).

@stakx
Copy link
Contributor

stakx commented Nov 26, 2017

This turned out to be quite difficult to implement, and this feature comes with a small runtime performance penalty, so some more optimization might be required later on.

@stakx stakx closed this as completed Nov 26, 2017
@stakx
Copy link
Contributor

stakx commented Dec 8, 2017

@JasonMing, @westonal, @kibiz0r, @fadookie, @jahmai, @almazik- I just published a pre-release version 4.8.0-rc1 on NuGet. It has (among other things) support for Task<> and ValueTask<> with both DefaultValue.Mock and DefaultValue.Empty. Feel free to test away.

@jahmai-ca
Copy link

@stakx Was Moq already returning Task.FromResult(default(T)) or am I hallucinating?

@stakx
Copy link
Contributor

stakx commented Dec 8, 2017

@jahmai - You're only partially hallucinating. 😉 Task support is more complete now. Moq has been happily wrapping default empty values (DefaultValue.Empty) in a completed task for a while. When you set DefaultValue.Mock, it would still wrap empty values inside a completed task, instead of mocked objects. This has been fixed now:

public abstract class Inner { }

public abstract class Outer
{
    public abstract Task<Inner> GetInnerAsync();
}

var mock = new Mock<Outer> { DefaultValue = DefaultValue.Mock };
var inner = await mock.Object.GetInnerAsync();
Assert.NotNull(inner); // NEW: This assertion used to fail, but now it passes.
                       // `inner` will not be `null`, but a mocked instance of `Inner`.

This makes it possible to directly await the return value of such methods. (Support for ValueTask<> works exactly the same.)

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

3 participants