-
Notifications
You must be signed in to change notification settings - Fork 759
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
Bug in fixer for xUnit2014 when handling nested methods #2870
Comments
The two failures are expected (you're calling a method marked with The incorrect method being modified is definitely a bug. |
Hi. I would like to start contributing to this project, and I am currently working on a solution for this issue. Adding the possibility of nested functions (local functions and/or anonymous functions, each of which may be invoked 0 or more times, possibly including recursive invocations) complicates the application of this rule. The documentation for xUnit2014 does not specify how these cases should be handled. I can see multiple ways they might be handled, including the two alternatives mentioned by @carlossanlop. Before continuing, I would like to confirm what the expected correct fix would be, and whether I should prioritize correctness or simplicity.
Consider [Fact]
public void TestMethod()
{
int OuterLocalFunction()
{
Func<bool> outerDelegate = () =>
{
string InnerLocalFunction()
{
Action innerDelegate = () =>
{
Assert.Throws<Exception>(() => Task.Delay(0));
};
innerDelegate();
innerDelegate();
string message = InnerLocalFunction(); // Recursion
return string.Empty;
}
string message = InnerLocalFunction();
InnerLocalFunction();
return false;
};
bool condition = outerDelegate();
outerDelegate();
return 0;
}
int number = OuterLocalFunction();
OuterLocalFunction();
} If we fully fix the method, with [Fact]
public async Task FullyFixedTestMethod()
{
async Task<int> OuterLocalFunction()
{
Func<Task<bool>> outerDelegate = async () =>
{
async Task<string> InnerLocalFunction()
{
Func<Task> innerDelegate = async () =>
{
await Assert.ThrowsAsync<Exception>(() => Task.Delay(0));
};
await innerDelegate();
await innerDelegate();
string message = await InnerLocalFunction(); // Recursion
return string.Empty;
}
string message = await InnerLocalFunction();
await InnerLocalFunction();
return false;
};
bool condition = await outerDelegate();
await outerDelegate();
return 0;
}
int number = await OuterLocalFunction();
await OuterLocalFunction();
} However, if we only fix the innermost level, then the result would be [Fact]
public void PartlyFixedTestMethod()
{
int OuterLocalFunction()
{
Func<bool> outerDelegate = () =>
{
string InnerLocalFunction()
{
Func<Task> innerDelegate = async () =>
{
await Assert.ThrowsAsync<Exception>(() => Task.Delay(0));
};
innerDelegate(); // Task is discarded and not awaited
innerDelegate(); // Task is discarded and not awaited
string message = InnerLocalFunction(); // Recursion
return string.Empty;
}
string message = InnerLocalFunction();
InnerLocalFunction();
return false;
};
bool condition = outerDelegate();
outerDelegate();
return 0;
}
int number = OuterLocalFunction();
OuterLocalFunction();
} Any guidance would be much appreciated. Thanks. |
You give two examples, but I think there's an in-between third option that actually provides a lot of value without a lot of extra work. What if you add the The fixed code would look something like this: [Fact]
public async Task PartlyFixedTestMethod()
{
async Task<int> OuterLocalFunction()
{
Func<Task<bool>> outerDelegate = async () =>
{
async Task<string> InnerLocalFunction()
{
Func<Task> innerDelegate = async () =>
{
await Assert.ThrowsAsync<Exception>(() => Task.Delay(0));
};
innerDelegate(); // Task is discarded and not awaited
innerDelegate(); // Task is discarded and not awaited
string message = InnerLocalFunction(); // Recursion
return string.Empty;
}
string message = InnerLocalFunction();
InnerLocalFunction();
return false;
};
bool condition = outerDelegate();
outerDelegate();
return 0;
}
int number = OuterLocalFunction();
OuterLocalFunction();
} And now here's what I see inside VS code: The yellow lines are CS1998 ( I think trying to do the rest of the fixing for them is over-ambitious and subject to a lot of edge cases. We can just let them do it themselves; we found the fundamental problem, and they can fix the rest.
Microsoft's advice is "always await, never just return", and while we agree to disagree (I think there are valid times to optimize), I think the decision to optimize is one to be left up to the developer. I'd say: always |
Yes, this makes sense and would certainly simplify the fixer compared to the other approach.
Yes, I agree that it would be better to use Thanks for your help. I will proceed to work on implementing this as you have described. |
Available in |
I encountered this unit test code in an old project using a nested method to assert a Task:
I got these two failures:
xUnit2014: Do not use Assert.Throws() to check for asynchronously thrown exceptions. Use Assert.ThrowsAsync instead.
CS0619: Assert.Throws<T>(Func<Task>) is obsolete: You must call Assert.ThrowsAsync<T> (and await the result) when testing async code.
Actual
The code fix was suggested by xUnit2014. Unfortunately, it is wrong: it adds the missing method modifiers to the top method:
Expected
The correct fix should have added the modifiers to the nested method, and then should've added the await keyword to the nested method invocation:
Or alternatively, not make the nested method async, return the value directly, but add await to the nested method invocation:
The text was updated successfully, but these errors were encountered: