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

Func<object?> overloads for new ThrowsExactly methods #5210

Closed
odebastiani opened this issue Mar 11, 2025 · 1 comment
Closed

Func<object?> overloads for new ThrowsExactly methods #5210

odebastiani opened this issue Mar 11, 2025 · 1 comment

Comments

@odebastiani
Copy link

Today I tried to update my tests to the latest MSTest version. I followed the recommendations and replaced Assert.ThrowsException with Assert.ThrowsExactly with the replace function of the IDE (code fixes takes too long for all instances). These methods were implemented with #4257 and #4350.
Unfortunately, the migration led to a couple errors because the new ThrowsExactly methods don't have an Func<object?> overload, even though ThrowsException does. These errors are easy to fix but lead to worse code and more work.

To reproduce the problems I had, you can use this class.

public sealed class TestClass
{
    private readonly bool _isInvalidState = true;

    public TestClass(string text)
    {
        ArgumentNullException.ThrowIfNull(text);
    }

    public void ThrowOnInvalidState()
    {
        if (_isInvalidState)
        {
            throw new InvalidOperationException("Invalid state");
        }
    }

    public string GetSomethingOThrowOnInvalidState()
    {
        if (_isInvalidState)
        {
            throw new InvalidOperationException("Invalid state");
        }

        return "Test";
    }

    public int this[int index]
    {
        get
        {
            ArgumentOutOfRangeException.ThrowIfLessThan(index, 0);

            return 1;
        }
    }
}

Testing constructors leads to the Info CA1806:

[TestMethod]
public void Constructors_ThrowsException()
{
    Assert.ThrowsException<ArgumentNullException>(() => new TestClass(null!));
}

[TestMethod]
public void Constructors_ThrowsExactly()
{
    // Changing from ThrowsException to ThrowsExactly leads to Info CA1806:
    // Test_ThrowsExactly creates a new instance of TestClass which is never used.
    // Pass the instance as an argument to another method, assign the instance to a variable, or
    // remove the object creation if it is unnecessary.
    Assert.ThrowsExactly<ArgumentNullException>(() => new TestClass(null!));

    // Possible fixes (worse code)
    TestClass? tempInstance = null;
    Assert.ThrowsExactly<ArgumentNullException>(() => tempInstance = new TestClass(null!));

    Assert.ThrowsExactly<ArgumentNullException>(() => _ = new TestClass(null!));
}

Methods that don't have parameters and return something didn't require a lambda expression. For those cases, replacing ThrowsException with ThrowsExactly without any manual fixes leads to Error CS407:

[TestMethod]
public void ReturningMethodsWithoutParameters_ThrowsException()
{
    TestClass instance = new TestClass("Test");
    Assert.ThrowsException<InvalidOperationException>(instance.GetSomethingOThrowOnInvalidState);
}

[TestMethod]
public void ReturningMethodsWithoutParameters_ThrowsExactly()
{
    TestClass instance = new TestClass("Test");

    // Replacing ThrowsException with ThrowsExactly leads to Error CS407:
    // 'string Tests.TestClass.GetSomethingOThrowOnInvalidState()' has the wrong return type
    //Assert.ThrowsExactly<InvalidOperationException>(instance.GetSomethingOThrowOnInvalidState);

    // Possible fix
    Assert.ThrowsExactly<InvalidOperationException>(() => instance.GetSomethingOThrowOnInvalidState());
}

Indexers also can't be replaced without manual fixes:

[TestMethod]
public void Indexers_ThrowsException()
{
    TestClass instance = new TestClass("Test");
    Assert.ThrowsException<ArgumentOutOfRangeException>(() => instance[-1]);
}

[TestMethod]
public void Indexers_ThrowsExactly()
{
    TestClass instance = new TestClass("Test");

    // Replacing ThrowsException with ThrowsExactly leads to Error CS0201:
    // Only assignment, call, increment, decrement, await, and new object expressions can be used as a statement
    //Assert.ThrowsExactly<ArgumentOutOfRangeException>(() => instance[-1]);

    // Possible fixes (worse code)
    int tempValue = 0;
    Assert.ThrowsExactly<ArgumentOutOfRangeException>(() => tempValue = instance[-1]);

    Assert.ThrowsExactly<ArgumentOutOfRangeException>(() => _ = instance[-1]);
}

A Func<object?> overload for the new ThrowsExactly and ThrowsExactlyAsync methods would be a good addition, would make the migration from ThrowsException easier and would improve the test code.

@Youssef1313
Copy link
Member

Duplicate of #5119.

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