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

SetReturnsDefault not working for Task<> #673

Closed
Albert221 opened this issue Sep 3, 2018 · 5 comments
Closed

SetReturnsDefault not working for Task<> #673

Albert221 opened this issue Sep 3, 2018 · 5 comments

Comments

@Albert221
Copy link
Contributor

Albert221 commented Sep 3, 2018

Explanation in comment.

public interface ITestInterface
{
    Task<bool> SomeMethod();
}

[Fact]
public async void TestBug()
{
    var mock = new Mock<ITestInterface>();
    mock
        .Setup(x => x.SomeMethod());

    mock.SetReturnsDefault(Task.FromResult(false));

    var obj = mock.Object;

    // `await` below throws NullReferenceException, because `obj.SomeMethod()` returns `null`
    Assert.False(await obj.SomeMethod());
}

Looks like Mock.SetReturnsDefault() doesn't like Task type, because obj.SomeMethod() returns null instead of Task<bool>. Although, if I add .ReturnsAsync(false) after .Setup (and remove SetReturnsDefault line) then everything is okay, obviously.

@stakx
Copy link
Contributor

stakx commented Sep 3, 2018

Hi @Albert221, there's no problem with SetReturnsDefault here that is specific to Task<T>. Instead, you are observing the somewhat non-intuitive way how Moq determines default return values for method calls.

  • If there is no matching setup for a method call:

    • If a default return value for the method's return type has been configured using SetReturnsDefault, then that value is used as the return value.

    • Otherwise, the default value provider (DefaultValue.Empty, DefaultValue.Mock) is responsible for producing a return value.

  • If there is a matching setup for a method call (such as in your case):

    • Any default return values set up by SetReturnsDefault are completely ignored.
    • If the setup mentions a CallBase or Returns clause, then the return value is determined by those.
    • Otherwise (such as in your case), the return value will be the default value of the return type—in C# parlance, default(T).

In your case, the very last of those bullet points applies. Since default(Task<bool>) is null, you get a NullReferenceException when awaiting it.

Basically, if you just remove the dummy setup for SomeMethod, the remainder of your code will work as expected. Alternatively, set up your setup's return value using Returns or similar (but you already knew that).

I know, that the above logic is ridiculously complex. Problem is, this cannot be easily rectified without possibly breaking existing user code.

@Albert221
Copy link
Contributor Author

Okay, it all makes sense now, thank you for your explanation!

I suggest adding this explanation (or reference to it) to XML documentation maybe? I've just searched for that in the Wiki, and it is present there, but I wonder whether it would be helpful and would save time for people like me :)

@stakx
Copy link
Contributor

stakx commented Sep 3, 2018

@Albert221: That's a good idea! I'd suggest that for now, we add a hint in the XML documentation for Mock.SetReturnsDefault saying that those default return values are used only in the absence of a setup for a method call.

Would you like to submit a PR?

@Albert221
Copy link
Contributor Author

Will do!

@stakx
Copy link
Contributor

stakx commented Sep 4, 2018

@Albert221: Thank you for contributing to Moq! 👍

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