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
staticunder 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.