Test Method Signature Valid

ID

csharp.test_method_signature_valid

Severity

critical

Remediation Complexity

trivial

Remediation Risk

low

Remediation Effort

low

Resource

Reliability

Language

CSharp

Tags

coverage, reliability, testing

Description

Reports a method that carries a test attribute but whose signature the framework cannot run. Four defects are recognised:

  • the method is not public — a test method written with no access modifier is private, which is the C# default for class members;

  • the method is static under a framework that only discovers instance methods;

  • the method declares parameters while its attribute form supplies none: [Fact], [Test] and [TestMethod] take no arguments, unlike [Theory], [TestCase] and [DataTestMethod], and unlike any method fed by a data-source attribute such as [InlineData], [TestCaseSource] or [DataRow] — or by an NUnit per-parameter data attribute ([Values], [ValueSource], [Range], [Random]), which NUnit accepts under a plain [Test] because it switches that method to data-driven execution the moment any one parameter carries it;

  • the method returns something the runner can neither await nor ignore — anything other than void, a task type or an iterator.

The return-type check stays silent when a test attribute was given arguments, since an attribute argument can declare the expected result and so make a returning test legitimate. Declarations with no body of their own — abstract, extern, partial — are skipped.

This rule applies to test code only.

Rationale

A runner that cannot bind to a method does not fail: it leaves the method out of the run. Nothing turns red, no message is printed in the usual output, and the only visible symptom is a test count that is one lower than it should be — which nobody watches. The behaviour the method was written to protect is silently unprotected from the moment the signature drifted.

The drift is easy to introduce: adding a parameter to a [Fact] without switching it to [Theory], pasting a helper’s private signature over a test’s, or making a fixture static while migrating it.

[Fact]
void SubtotalIsSummed()                     // FLAW - not public, so xUnit never runs it
{
    Assert.Equal(3, Basket.Subtotal(1, 2));
}

[Fact]
public void ShippingIsFree(int weight)      // FLAW - [Fact] supplies no arguments
{
    Assert.True(Basket.FreeShipping(weight));
}

[TestMethod]
public static void CouponIsApplied()        // FLAW - MSTest discovers instance methods only
{
    Assert.IsTrue(Basket.Coupon("XY"));
}

[Test]
public int TaxIsRounded()                   // FLAW - the runner cannot use the returned value
{
    return Basket.Tax(100);
}

[Theory]
[InlineData(2)]
public void QuantityIsPositive(int quantity)  // OK - a theory is fed its arguments
{
    Assert.True(quantity > 0);
}

[Test]
public void AccountIdIsValidated([Values(null, "", "  ")] string accountId) // OK - [Values] on the
{                                                                            // parameter supplies it
    Assert.True(accountId != "unreachable");
}

[Test(ExpectedResult = 3)]
public int SubtotalIsThree()                // OK - the attribute declares the expected result
{
    return Basket.Subtotal(1, 2);
}

[Fact]
public async Task BasketIsSavedAsync()      // OK - the runner awaits the task
{
    await Basket.SaveAsync();
}

Remediation

Make the signature match what the attribute promises. Add the missing public; drop static where the framework requires an instance; switch a parameterised test to the attribute form that supplies its arguments ([Theory] with [InlineData], [TestCase], [DataRow]); and return void or Task unless the attribute declares an expected result.

After fixing, check the executed-test count actually went up — that is the only confirmation that the method is now being discovered.