Skip to content

Commit 85a1a7c

Browse files
Fix issue with tests failing with [TestCase] or [InlineData]
1 parent 72d9648 commit 85a1a7c

2 files changed

Lines changed: 66 additions & 10 deletions

File tree

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
using Shouldly;
2+
using Xunit;
3+
4+
namespace TestStack.BDDfy.Tests.Scanner.Examples
5+
{
6+
/// <summary>
7+
/// Verifies that fluent steps using closure-captured method parameters
8+
/// from [InlineData] / [TestCase] retain correct values at execution time.
9+
/// Regression test: ParameterizedTestDetector must not overwrite closure values
10+
/// when the parameter is not stored as a field on the test object.
11+
/// </summary>
12+
public class FluentClosureParameterTests
13+
{
14+
private string? _capturedRoute;
15+
private int _capturedStatus;
16+
17+
[Theory]
18+
[InlineData("/api/orders", 200)]
19+
[InlineData("/api/missing", 404)]
20+
public void Multi_parameter_values_should_not_be_corrupted(string endpoint, int statusCode)
21+
{
22+
this.Given(_ => A_request_is_made_to(endpoint))
23+
.When(_ => The_response_status_is(statusCode))
24+
.Then(_ => The_resolved_route_should_be(endpoint))
25+
.And(_ => The_status_should_be(statusCode))
26+
.BDDfy();
27+
}
28+
29+
private void A_request_is_made_to(string route)
30+
{
31+
_capturedRoute = route;
32+
}
33+
34+
private void The_response_status_is(int statusCode)
35+
{
36+
_capturedStatus = statusCode;
37+
}
38+
39+
private void The_resolved_route_should_be(params string[] expected)
40+
{
41+
new[] { _capturedRoute }.ShouldBe(expected);
42+
}
43+
44+
private void The_status_should_be(int expected)
45+
{
46+
_capturedStatus.ShouldBe(expected);
47+
}
48+
}
49+
}

src/TestStack.BDDfy/Scanners/ScenarioScanners/ParameterizedTestDetector.cs

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,6 @@ internal static class ParameterizedTestDetector
4545

4646
// Build example from current parameter values (resolved from the appropriate object)
4747
var example = BuildExampleFromParameters(declaringObject!, parameters);
48-
if (example is null) return null;
4948

5049
// Stable ID based on declaring type + method name (shared across invocations)
5150
var declaringType = method.DeclaringType ?? testType;
@@ -123,26 +122,35 @@ private static bool IsParameterizedTestMethod(MethodInfo method)
123122
foreach (var param in parameters)
124123
{
125124
var paramName = param.Name!;
126-
object? value = ResolveParameterValue(testObject, type, paramName, bindingFlags);
125+
if (!TryResolveParameterValue(testObject, type, paramName, bindingFlags, out var value))
126+
return null; // Can't resolve all parameters — skip Example creation
127+
127128
values.Add(new ExampleValue(paramName, value, () => rowIndex));
128129
}
129130

130131
return values.Count > 0 ? new Example([.. values]) : null;
131132
}
132133

133-
private static object? ResolveParameterValue(object testObject, Type type, string paramName, BindingFlags flags)
134+
private static bool TryResolveParameterValue(object testObject, Type type, string paramName, BindingFlags flags, out object? value)
134135
{
135136
// Try field (including backing fields from primary constructors)
136137
var field = FindField(type, paramName, flags);
137138
if (field is not null)
138-
return field.GetValue(testObject);
139+
{
140+
value = field.GetValue(testObject);
141+
return true;
142+
}
139143

140144
// Try property
141145
var prop = FindProperty(type, paramName, flags);
142146
if (prop is not null)
143-
return prop.GetValue(testObject);
147+
{
148+
value = prop.GetValue(testObject);
149+
return true;
150+
}
144151

145-
return null;
152+
value = null;
153+
return false;
146154
}
147155

148156
private static FieldInfo? FindField(Type type, string name, BindingFlags flags)
@@ -159,8 +167,7 @@ private static bool IsParameterizedTestMethod(MethodInfo method)
159167
var fields = type.GetFields(flags);
160168
foreach (var f in fields)
161169
{
162-
if (f.Name.Equals($"<{name}>P", StringComparison.Ordinal) ||
163-
f.Name.Contains(name, StringComparison.OrdinalIgnoreCase))
170+
if (f.Name.Equals($"<{name}>P", StringComparison.Ordinal))
164171
return f;
165172
}
166173

@@ -180,10 +187,10 @@ private static bool IsParameterizedTestMethod(MethodInfo method)
180187
}
181188
}
182189

183-
internal sealed class ParameterizedTestInfo(MethodInfo method, string stableScenarioId, Example example)
190+
internal sealed class ParameterizedTestInfo(MethodInfo method, string stableScenarioId, Example? example)
184191
{
185192
public MethodInfo Method { get; } = method;
186193
public string StableScenarioId { get; } = stableScenarioId;
187-
public Example Example { get; } = example;
194+
public Example? Example { get; } = example;
188195
}
189196
}

0 commit comments

Comments
 (0)