From 0e8d1b91e9c42f0e7f84bd1655bf95e898502f5f Mon Sep 17 00:00:00 2001 From: Gurpreet Singh Date: Tue, 26 May 2026 00:09:20 +0100 Subject: [PATCH 1/3] enable controling inputs in step title from step title factory setting --- .../CanRunAsyncSteps.cs | 1 + .../Scanner/FluentScanner/StepTitleTests.cs | 80 +++++++++++++++---- .../UsingCustomStepTitleFactory.cs | 8 +- .../WhenStepsAreScannedUsingFluentScanner.cs | 2 + .../Abstractions/DefaultStepTitleFactory.cs | 22 ++--- .../Abstractions/IStepTitleFactory.cs | 4 +- .../ArgumentCleaningExtensions.cs | 19 ++--- .../Fluent/ExpressionExtensions.cs | 30 +++++-- .../Scanners/StepScanners/Fluent/FluentApi.cs | 48 +++++------ .../StepScanners/Fluent/FluentScanner.cs | 29 +++---- 10 files changed, 152 insertions(+), 91 deletions(-) diff --git a/src/Samples/TestStack.BDDfy.Samples/CanRunAsyncSteps.cs b/src/Samples/TestStack.BDDfy.Samples/CanRunAsyncSteps.cs index 47516cc1..0d42f864 100644 --- a/src/Samples/TestStack.BDDfy.Samples/CanRunAsyncSteps.cs +++ b/src/Samples/TestStack.BDDfy.Samples/CanRunAsyncSteps.cs @@ -1,6 +1,7 @@ using System; using System.Threading.Tasks; using Shouldly; +using TestStack.BDDfy.Configuration; using Xunit; namespace TestStack.BDDfy.Samples diff --git a/src/TestStack.BDDfy.Tests/Scanner/FluentScanner/StepTitleTests.cs b/src/TestStack.BDDfy.Tests/Scanner/FluentScanner/StepTitleTests.cs index e405fc62..03480177 100644 --- a/src/TestStack.BDDfy.Tests/Scanner/FluentScanner/StepTitleTests.cs +++ b/src/TestStack.BDDfy.Tests/Scanner/FluentScanner/StepTitleTests.cs @@ -1,5 +1,6 @@ using System.Linq; using Shouldly; +using TestStack.BDDfy.Configuration; using TestStack.BDDfy.Tests.Concurrency; using Xunit; @@ -8,7 +9,43 @@ namespace TestStack.BDDfy.Tests.Scanner.FluentScanner [Collection(TestCollectionName.ModifiesConfigurator)] public class StepTitleTests { - private string _mutatedState; + private string _state; + + [Fact] + public void UseConfiguration_IncludeInputsInStepTitle() + { + try + { + Configurator.StepTitleFactory.IncludeInputsInStepTitle = false; + FooClass something = new(); + var story = this + .When(_ => something.Sub.ActionWithArgument("foo")) + .And(_ => something.Sub.ActionWithArgumentsDisabledInTitle("foo")) + .And(_ => something.Sub.ActionWithTemplateTitleAndArguments("foo")) + .And(_ => something.Sub.ActionWithArgumentsEnabledInTitle("foo")) + .BDDfy(); + + var actualTitles = story.Scenarios.Single().Steps.Select(s => s.Title).ToArray(); + var expectedTitles = new[] + { + "When with arg", + "And with arg", + "And with foo arg", + "And with arg foo" + }; + + actualTitles.ShouldBeEquivalentTo(expectedTitles); ; + } + catch + { + throw; + } + finally + { + Configurator.StepTitleFactory.IncludeInputsInStepTitle = true; + } + + } [Fact] public void MethodCallInStepTitle() @@ -19,16 +56,24 @@ public void MethodCallInStepTitle() .When(_ => something.Sub.SomethingHappens()) .And(_ => something.Sub.SomethingWithDifferentTitle()) .Then(_ => ThenTitleHas(AMethodCall())) - .And(_ => something.Sub.SomethingWithArg("foo")) - .And(_ => something.Sub.SomethingWithArg2("foo")) - .And(_ => something.Sub.SomethingWithArg3("foo")) + .And(_ => something.Sub.ActionWithArgument("foo")) + .And(_ => something.Sub.ActionWithArgumentsDisabledInTitle("foo")) + .And(_ => something.Sub.ActionWithTemplateTitleAndArguments("foo")) .BDDfy(); - story.Scenarios.Single().Steps.ElementAt(2).Title.ShouldBe("And different title"); - story.Scenarios.Single().Steps.ElementAt(3).Title.ShouldBe("Then title has Mutated state"); - story.Scenarios.Single().Steps.ElementAt(4).Title.ShouldBe("And with arg foo"); - story.Scenarios.Single().Steps.ElementAt(5).Title.ShouldBe("And with arg"); - story.Scenarios.Single().Steps.ElementAt(6).Title.ShouldBe("And with foo arg"); + var actualTitles = story.Scenarios.Single().Steps.Select(s => s.Title).ToArray(); + var expectedTitles = new[] + { + "Given we mutate some state", + "When something happens", + "And different title", + "Then title has Mutated state", + "And with arg foo", + "And with arg", + "And with foo arg" + }; + + actualTitles.ShouldBeEquivalentTo(expectedTitles); } public class FooClass @@ -54,34 +99,39 @@ public void SomethingWithDifferentTitle() } [StepTitle("With arg")] - public void SomethingWithArg(string arg) + public void ActionWithArgument(string arg) { } [StepTitle("With arg", false)] - public void SomethingWithArg2(string arg) + public void ActionWithArgumentsDisabledInTitle(string arg) + { + } + + [StepTitle("With arg", true)] + public void ActionWithArgumentsEnabledInTitle(string arg) { } [StepTitle("With {0} arg", false)] - public void SomethingWithArg3(string arg) + public void ActionWithTemplateTitleAndArguments(string arg) { } } private string AMethodCall() { - return _mutatedState; + return _state; } private void GivenWeMutateSomeState() { - _mutatedState = "Mutated state"; + _state = "Mutated state"; } private void ThenTitleHas(string result) { - result.ShouldBe(_mutatedState); + result.ShouldBe(_state); } } } \ No newline at end of file diff --git a/src/TestStack.BDDfy.Tests/Scanner/FluentScanner/UsingCustomStepTitleFactory.cs b/src/TestStack.BDDfy.Tests/Scanner/FluentScanner/UsingCustomStepTitleFactory.cs index 0a303360..2624fabf 100644 --- a/src/TestStack.BDDfy.Tests/Scanner/FluentScanner/UsingCustomStepTitleFactory.cs +++ b/src/TestStack.BDDfy.Tests/Scanner/FluentScanner/UsingCustomStepTitleFactory.cs @@ -13,15 +13,17 @@ public class UsingCustomStepTitleFactory { private class CustomStepTitleFactory : IStepTitleFactory { + public bool IncludeInputsInStepTitle { get; set; } = true; + public StepTitle Create( string stepTextTemplate, - bool includeInputsInStepTitle, + bool? includeInputsInStepTitle, MethodInfo methodInfo, StepArgument[] inputArguments, ITestContext testContext, - string stepPrefix) => new StepTitle("Custom Step Title"); + string stepPrefix) => new("Custom Step Title"); - public StepTitle Create(string title, string stepPrefix, ITestContext testContext) => new StepTitle(title); + public StepTitle Create(string title, string stepPrefix, ITestContext testContext) => new(title); } [Fact] diff --git a/src/TestStack.BDDfy.Tests/Scanner/FluentScanner/WhenStepsAreScannedUsingFluentScanner.cs b/src/TestStack.BDDfy.Tests/Scanner/FluentScanner/WhenStepsAreScannedUsingFluentScanner.cs index 032dda2a..cddd0e74 100644 --- a/src/TestStack.BDDfy.Tests/Scanner/FluentScanner/WhenStepsAreScannedUsingFluentScanner.cs +++ b/src/TestStack.BDDfy.Tests/Scanner/FluentScanner/WhenStepsAreScannedUsingFluentScanner.cs @@ -265,6 +265,8 @@ Step AndInputsAreFormattedPropertlyInTheTitle ScenarioToBeScannedUsingFluentScanner.InputDateStepTitleTemplate, ScenarioToBeScannedUsingFluentScanner.InputDate); + var titles = _steps.Select(x=>x.Title.Trim()).ToArray(); + return _steps.Single(s => s.Title.Trim() == formattedTitle); } } diff --git a/src/TestStack.BDDfy/Abstractions/DefaultStepTitleFactory.cs b/src/TestStack.BDDfy/Abstractions/DefaultStepTitleFactory.cs index 51c7c433..847c34e9 100644 --- a/src/TestStack.BDDfy/Abstractions/DefaultStepTitleFactory.cs +++ b/src/TestStack.BDDfy/Abstractions/DefaultStepTitleFactory.cs @@ -7,31 +7,32 @@ namespace TestStack.BDDfy.Abstractions; internal class DefaultStepTitleFactory : IStepTitleFactory { + public bool IncludeInputsInStepTitle { get; set; } = true; + public StepTitle Create( string stepTextTemplate, - bool includeInputsInStepTitle, + bool? includeInputsInStepTitle, MethodInfo methodInfo, StepArgument[] inputArguments, ITestContext testContext, string stepPrefix) { - Func createTitle = () => + string createTitle() { var flatInputArray = inputArguments.Select(o => o.Value).FlattenArrays(); var name = methodInfo.Name; - var stepTitleAttribute = methodInfo.GetCustomAttributes(typeof(StepTitleAttribute), true).SingleOrDefault(); - if (stepTitleAttribute != null) + var titleAttribute = methodInfo.GetCustomAttribute(true); + includeInputsInStepTitle ??= titleAttribute?.IncludeInputsInStepTitle ?? IncludeInputsInStepTitle; + + if (titleAttribute is not null) { - var titleAttribute = ((StepTitleAttribute)stepTitleAttribute); name = string.Format(titleAttribute.StepTitle, flatInputArray); - if (titleAttribute.IncludeInputsInStepTitle != null) - includeInputsInStepTitle = titleAttribute.IncludeInputsInStepTitle.Value; } var stepTitle = AppendPrefix(Configurator.Humanizer.Humanize(name), stepPrefix); if (!string.IsNullOrEmpty(stepTextTemplate)) stepTitle = string.Format(stepTextTemplate, flatInputArray); - else if (includeInputsInStepTitle) + else if (includeInputsInStepTitle.Value) { var parameters = methodInfo.GetParameters(); var stringFlatInputs = @@ -56,16 +57,17 @@ public StepTitle Create( return i.Value.Value.FlattenArray(); }) .ToArray(); + stepTitle = stepTitle + " " + string.Join(", ", stringFlatInputs); } return stepTitle.Trim(); - }; + } return new StepTitle(createTitle); } - public StepTitle Create(string title, string stepPrefix, ITestContext testContext) => new StepTitle(AppendPrefix(title, stepPrefix)); + public StepTitle Create(string title, string stepPrefix, ITestContext testContext) => new(AppendPrefix(title, stepPrefix)); private static string AppendPrefix(string title, string stepPrefix) { diff --git a/src/TestStack.BDDfy/Abstractions/IStepTitleFactory.cs b/src/TestStack.BDDfy/Abstractions/IStepTitleFactory.cs index 55365c19..14c16bd5 100644 --- a/src/TestStack.BDDfy/Abstractions/IStepTitleFactory.cs +++ b/src/TestStack.BDDfy/Abstractions/IStepTitleFactory.cs @@ -4,9 +4,11 @@ namespace TestStack.BDDfy.Abstractions; public interface IStepTitleFactory { + bool IncludeInputsInStepTitle { get; set; } + public StepTitle Create( string stepTextTemplate, - bool includeInputsInStepTitle, + bool? includeInputsInStepTitle, MethodInfo methodInfo, StepArgument[] inputArguments, ITestContext testContext, diff --git a/src/TestStack.BDDfy/Scanners/StepScanners/ArgumentCleaningExtensions.cs b/src/TestStack.BDDfy/Scanners/StepScanners/ArgumentCleaningExtensions.cs index 2cc43039..73d2f43c 100644 --- a/src/TestStack.BDDfy/Scanners/StepScanners/ArgumentCleaningExtensions.cs +++ b/src/TestStack.BDDfy/Scanners/StepScanners/ArgumentCleaningExtensions.cs @@ -8,29 +8,20 @@ internal static class ArgumentCleaningExtensions { internal static object[] FlattenArrays(this IEnumerable inputs) { - return inputs.Select(FlattenArray).ToArray(); + return [.. inputs.Select(FlattenArray)]; } public static object FlattenArray(this object input) { - var inputArray = input as Array; - if (inputArray != null) + if (input is Array inputArray) { - var temp = (from object arrElement in inputArray select GetSafeString(arrElement)).ToArray(); + var temp = from object arrElement in inputArray select GetSafeValue(arrElement); return string.Join(", ", temp); } - if (input == null) return "'null'"; - - return input; + return GetSafeValue(input); } - static string GetSafeString(object input) - { - if (input == null) - return "'null'"; - - return input.ToString(); - } + static object GetSafeValue(object input) => input ?? "'null'"; } } \ No newline at end of file diff --git a/src/TestStack.BDDfy/Scanners/StepScanners/Fluent/ExpressionExtensions.cs b/src/TestStack.BDDfy/Scanners/StepScanners/Fluent/ExpressionExtensions.cs index 471e7632..c5eabc5e 100644 --- a/src/TestStack.BDDfy/Scanners/StepScanners/Fluent/ExpressionExtensions.cs +++ b/src/TestStack.BDDfy/Scanners/StepScanners/Fluent/ExpressionExtensions.cs @@ -22,10 +22,12 @@ public static IEnumerable ExtractArguments(this Expression _arguments; + private object _value; public IEnumerable ExtractArguments(LambdaExpression methodCallExpression, object value) { - _arguments = new List(); + _arguments = []; + _value = value; Visit(methodCallExpression); return _arguments; } @@ -37,7 +39,7 @@ protected override Expression VisitMethodCall(MethodCallExpression node) return node; } - private static StepArgument ExtractStepArgument(Expression a) + private StepArgument ExtractStepArgument(Expression a) { switch (a.NodeType) { @@ -73,17 +75,33 @@ private static StepArgument ExtractStepArgument(Expression a) } } - private static Func GetValue(Expression a) + private Func GetValue(Expression a) { + // If the expression is a member access on the lambda parameter (e.g. _ => _.Prop) + // replace the parameter with the supplied _value so the compiled delegate can be invoked + if (a is MemberExpression memberExpression && memberExpression.Expression is ParameterExpression) + { + var replaced = Expression.Convert(Expression.MakeMemberAccess(Expression.Constant(_value), memberExpression.Member), typeof(object)); + return Expression.Lambda>(replaced).Compile(); + } + return Expression.Lambda>(Expression.Convert(a, typeof(object))).Compile(); } - private static Action SetValue(Expression a, Type parameterType) + private Action SetValue(Expression a, Type parameterType) { var parameter = Expression.Parameter(typeof(object)); var unaryExpression = Expression.Convert(parameter, parameterType); - var assign = Expression.Assign(a, unaryExpression); - return Expression.Lambda>(assign, parameter).Compile(); + + if (a is MemberExpression memberExpression && memberExpression.Expression is ParameterExpression) + { + var memberAccess = Expression.MakeMemberAccess(Expression.Constant(_value), memberExpression.Member); + var assign = Expression.Assign(memberAccess, unaryExpression); + return Expression.Lambda>(assign, parameter).Compile(); + } + + var assignDefault = Expression.Assign(a, unaryExpression); + return Expression.Lambda>(assignDefault, parameter).Compile(); } } } diff --git a/src/TestStack.BDDfy/Scanners/StepScanners/Fluent/FluentApi.cs b/src/TestStack.BDDfy/Scanners/StepScanners/Fluent/FluentApi.cs index c1fb10f1..1d67a9c2 100644 --- a/src/TestStack.BDDfy/Scanners/StepScanners/Fluent/FluentApi.cs +++ b/src/TestStack.BDDfy/Scanners/StepScanners/Fluent/FluentApi.cs @@ -331,7 +331,7 @@ public FluentStepBuilder(TScenario testObject) public IFluentStepBuilder Given(Expression> step, string stepTextTemplate) { - scanner.AddStep(step, stepTextTemplate, true, true, ExecutionOrder.SetupState, false, "Given"); + scanner.AddStep(step, stepTextTemplate, null, true, ExecutionOrder.SetupState, false, "Given"); return this; } @@ -343,13 +343,13 @@ public IFluentStepBuilder Given(Expression> step, b public IFluentStepBuilder Given(Expression> step) { - scanner.AddStep(step, null, true, true, ExecutionOrder.SetupState, false, "Given"); + scanner.AddStep(step, null, null, true, ExecutionOrder.SetupState, false, "Given"); return this; } public IFluentStepBuilder Given(Expression> step, string stepTextTemplate) { - scanner.AddStep(step, stepTextTemplate, true, true, ExecutionOrder.SetupState, false, "Given"); + scanner.AddStep(step, stepTextTemplate, null, true, ExecutionOrder.SetupState, false, "Given"); return this; } @@ -361,7 +361,7 @@ public IFluentStepBuilder Given(Expression> ste public IFluentStepBuilder Given(Expression> step) { - scanner.AddStep(step, null, true, true, ExecutionOrder.SetupState, false, "Given"); + scanner.AddStep(step, null, null, true, ExecutionOrder.SetupState, false, "Given"); return this; } @@ -390,7 +390,7 @@ public IFluentStepBuilder Given(string title) } public IFluentStepBuilder When(Expression> step, string stepTextTemplate) { - scanner.AddStep(step, stepTextTemplate, true, true, ExecutionOrder.Transition, false, "When"); + scanner.AddStep(step, stepTextTemplate, null, true, ExecutionOrder.Transition, false, "When"); return this; } @@ -402,13 +402,13 @@ public IFluentStepBuilder When(Expression> step, bo public IFluentStepBuilder When(Expression> step) { - scanner.AddStep(step, null, true, true, ExecutionOrder.Transition, false, "When"); + scanner.AddStep(step, null, null, true, ExecutionOrder.Transition, false, "When"); return this; } public IFluentStepBuilder When(Expression> step, string stepTextTemplate) { - scanner.AddStep(step, stepTextTemplate, true, true, ExecutionOrder.Transition, false, "When"); + scanner.AddStep(step, stepTextTemplate, null, true, ExecutionOrder.Transition, false, "When"); return this; } @@ -420,7 +420,7 @@ public IFluentStepBuilder When(Expression> step public IFluentStepBuilder When(Expression> step) { - scanner.AddStep(step, null, true, true, ExecutionOrder.Transition, false, "When"); + scanner.AddStep(step, null, null, true, ExecutionOrder.Transition, false, "When"); return this; } @@ -449,7 +449,7 @@ public IFluentStepBuilder When(string title) } public IFluentStepBuilder Then(Expression> step, string stepTextTemplate) { - scanner.AddStep(step, stepTextTemplate, true, true, ExecutionOrder.Assertion, true, "Then"); + scanner.AddStep(step, stepTextTemplate, null, true, ExecutionOrder.Assertion, true, "Then"); return this; } @@ -461,13 +461,13 @@ public IFluentStepBuilder Then(Expression> step, bo public IFluentStepBuilder Then(Expression> step) { - scanner.AddStep(step, null, true, true, ExecutionOrder.Assertion, true, "Then"); + scanner.AddStep(step, null, null, true, ExecutionOrder.Assertion, true, "Then"); return this; } public IFluentStepBuilder Then(Expression> step, string stepTextTemplate) { - scanner.AddStep(step, stepTextTemplate, true, true, ExecutionOrder.Assertion, true, "Then"); + scanner.AddStep(step, stepTextTemplate, null, true, ExecutionOrder.Assertion, true, "Then"); return this; } @@ -479,7 +479,7 @@ public IFluentStepBuilder Then(Expression> step public IFluentStepBuilder Then(Expression> step) { - scanner.AddStep(step, null, true, true, ExecutionOrder.Assertion, true, "Then"); + scanner.AddStep(step, null, null, true, ExecutionOrder.Assertion, true, "Then"); return this; } @@ -508,7 +508,7 @@ public IFluentStepBuilder Then(string title) } public IFluentStepBuilder And(Expression> step, string stepTextTemplate) { - scanner.AddStep(step, stepTextTemplate, true, true, ExecutionOrder.ConsecutiveStep, false, "And"); + scanner.AddStep(step, stepTextTemplate, null, true, ExecutionOrder.ConsecutiveStep, false, "And"); return this; } @@ -520,13 +520,13 @@ public IFluentStepBuilder And(Expression> step, boo public IFluentStepBuilder And(Expression> step) { - scanner.AddStep(step, null, true, true, ExecutionOrder.ConsecutiveStep, false, "And"); + scanner.AddStep(step, null, null, true, ExecutionOrder.ConsecutiveStep, false, "And"); return this; } public IFluentStepBuilder And(Expression> step, string stepTextTemplate) { - scanner.AddStep(step, stepTextTemplate, true, true, ExecutionOrder.ConsecutiveStep, false, "And"); + scanner.AddStep(step, stepTextTemplate, null, true, ExecutionOrder.ConsecutiveStep, false, "And"); return this; } @@ -538,7 +538,7 @@ public IFluentStepBuilder And(Expression> step, public IFluentStepBuilder And(Expression> step) { - scanner.AddStep(step, null, true, true, ExecutionOrder.ConsecutiveStep, false, "And"); + scanner.AddStep(step, null, null, true, ExecutionOrder.ConsecutiveStep, false, "And"); return this; } @@ -567,7 +567,7 @@ public IFluentStepBuilder And(string title) } public IFluentStepBuilder But(Expression> step, string stepTextTemplate) { - scanner.AddStep(step, stepTextTemplate, true, true, ExecutionOrder.ConsecutiveStep, false, "But"); + scanner.AddStep(step, stepTextTemplate, null, true, ExecutionOrder.ConsecutiveStep, false, "But"); return this; } @@ -579,13 +579,13 @@ public IFluentStepBuilder But(Expression> step, boo public IFluentStepBuilder But(Expression> step) { - scanner.AddStep(step, null, true, true, ExecutionOrder.ConsecutiveStep, false, "But"); + scanner.AddStep(step, null, null, true, ExecutionOrder.ConsecutiveStep, false, "But"); return this; } public IFluentStepBuilder But(Expression> step, string stepTextTemplate) { - scanner.AddStep(step, stepTextTemplate, true, true, ExecutionOrder.ConsecutiveStep, false, "But"); + scanner.AddStep(step, stepTextTemplate, null, true, ExecutionOrder.ConsecutiveStep, false, "But"); return this; } @@ -597,7 +597,7 @@ public IFluentStepBuilder But(Expression> step, public IFluentStepBuilder But(Expression> step) { - scanner.AddStep(step, null, true, true, ExecutionOrder.ConsecutiveStep, false, "But"); + scanner.AddStep(step, null, null, true, ExecutionOrder.ConsecutiveStep, false, "But"); return this; } @@ -626,7 +626,7 @@ public IFluentStepBuilder But(string title) } public IFluentStepBuilder TearDownWith(Expression> step, string stepTextTemplate) { - scanner.AddStep(step, stepTextTemplate, true, false, ExecutionOrder.TearDown, false, ""); + scanner.AddStep(step, stepTextTemplate, null, false, ExecutionOrder.TearDown, false, ""); return this; } @@ -638,13 +638,13 @@ public IFluentStepBuilder TearDownWith(Expression> public IFluentStepBuilder TearDownWith(Expression> step) { - scanner.AddStep(step, null, true, false, ExecutionOrder.TearDown, false, ""); + scanner.AddStep(step, null, null, false, ExecutionOrder.TearDown, false, ""); return this; } public IFluentStepBuilder TearDownWith(Expression> step, string stepTextTemplate) { - scanner.AddStep(step, stepTextTemplate, true, false, ExecutionOrder.TearDown, false, ""); + scanner.AddStep(step, stepTextTemplate, null, false, ExecutionOrder.TearDown, false, ""); return this; } @@ -656,7 +656,7 @@ public IFluentStepBuilder TearDownWith(Expression TearDownWith(Expression> step) { - scanner.AddStep(step, null, true, false, ExecutionOrder.TearDown, false, ""); + scanner.AddStep(step, null, null, false, ExecutionOrder.TearDown, false, ""); return this; } diff --git a/src/TestStack.BDDfy/Scanners/StepScanners/Fluent/FluentScanner.cs b/src/TestStack.BDDfy/Scanners/StepScanners/Fluent/FluentScanner.cs index 6ac42e08..15fd6c27 100644 --- a/src/TestStack.BDDfy/Scanners/StepScanners/Fluent/FluentScanner.cs +++ b/src/TestStack.BDDfy/Scanners/StepScanners/Fluent/FluentScanner.cs @@ -12,7 +12,7 @@ namespace TestStack.BDDfy internal class FluentScanner : IFluentScanner where TScenario : class { - private readonly List _steps = new(); + private readonly List _steps = []; private readonly TScenario _testObject; private readonly ITestContext _testContext; private readonly MethodInfo _fakeExecuteActionMethod; @@ -33,14 +33,14 @@ public void AddStep(Action stepAction, string title, bool reports, ExecutionOrde { var action = StepActionFactory.GetStepAction(o => stepAction()); var stepTitle = CreateTitle(title, stepPrefix); - _steps.Add(new Step(action, stepTitle, FixAsserts(asserts, executionOrder), FixConsecutiveStep(executionOrder), reports, new List())); + _steps.Add(new Step(action, stepTitle, FixAsserts(asserts, executionOrder), FixConsecutiveStep(executionOrder), reports, [])); } public void AddStep(Func stepAction, string title, bool reports, ExecutionOrder executionOrder, bool asserts, string stepPrefix) { var action = StepActionFactory.GetStepAction(o => stepAction()); var stepTitle = CreateTitle(title, stepPrefix); - _steps.Add(new Step(action, stepTitle, FixAsserts(asserts, executionOrder), FixConsecutiveStep(executionOrder), reports, new List())); + _steps.Add(new Step(action, stepTitle, FixAsserts(asserts, executionOrder), FixConsecutiveStep(executionOrder), reports, [])); } private StepTitle CreateTitle(string title, string stepPrefix) => Configurator.StepTitleFactory.Create(title, stepPrefix, _testContext); @@ -64,18 +64,14 @@ private void ExecuteAction(ExampleAction _) public void AddStep( Expression> stepAction, string stepTextTemplate, - bool includeInputsInStepTitle, + bool? includeInputsInStepTitle, bool reports, ExecutionOrder executionOrder, bool asserts, string stepPrefix) { var action = stepAction.Compile(); - var inputArguments = Array.Empty(); - if (includeInputsInStepTitle) - { - inputArguments = stepAction.ExtractArguments(_testObject).ToArray(); - } + var inputArguments = stepAction.ExtractArguments(_testObject).ToArray(); var title = CreateTitle( stepTextTemplate, @@ -84,7 +80,7 @@ public void AddStep( inputArguments, stepPrefix); - var args = inputArguments.Where(s => !string.IsNullOrEmpty(s.Name)).ToList(); + var args = inputArguments.Where(s => !string.IsNullOrWhiteSpace(s.Name)).ToList(); var stepDelegate = StepActionFactory.GetStepAction(action); var shouldFixAsserts = FixAsserts(asserts, executionOrder); var shouldFixConsecutiveStep = FixConsecutiveStep(executionOrder); @@ -95,7 +91,7 @@ public void AddStep( public void AddStep( Expression> stepAction, string stepTextTemplate, - bool includeInputsInStepTitle, + bool? includeInputsInStepTitle, bool reports, ExecutionOrder executionOrder, bool asserts, @@ -106,25 +102,22 @@ public void AddStep( AddStep(action, stepAction, stepTextTemplate, includeInputsInStepTitle, reports, executionOrder, asserts, stepPrefix); } - private void AddStep(Action action, LambdaExpression stepAction, string stepTextTemplate, bool includeInputsInStepTitle, + private void AddStep(Action action, LambdaExpression stepAction, string stepTextTemplate, bool? includeInputsInStepTitle, bool reports, ExecutionOrder executionOrder, bool asserts, string stepPrefix) { - var inputArguments = Array.Empty(); - if (includeInputsInStepTitle) - { - inputArguments = stepAction.ExtractArguments(_testObject).ToArray(); - } + var inputArguments = stepAction.ExtractArguments(_testObject).ToArray(); var title = CreateTitle(stepTextTemplate, includeInputsInStepTitle, GetMethodInfo(stepAction), inputArguments, stepPrefix); var args = inputArguments.Where(s => !string.IsNullOrEmpty(s.Name)).ToList(); + _steps.Add(new Step(StepActionFactory.GetStepAction(action), title, FixAsserts(asserts, executionOrder), FixConsecutiveStep(executionOrder), reports, args)); } private StepTitle CreateTitle( string stepTextTemplate, - bool includeInputsInStepTitle, + bool? includeInputsInStepTitle, MethodInfo methodInfo, StepArgument[] inputArguments, string stepPrefix) From 346c4c47d31a500ea9eb9b21d1346c24f833f502 Mon Sep 17 00:00:00 2001 From: Gurpreet Singh Date: Tue, 26 May 2026 00:37:32 +0100 Subject: [PATCH 2/3] use step title from executable attribute if StepTitle attribute is not given --- .../Scanner/FluentScanner/StepTitleTests.cs | 6 ++++++ .../WhenStepsAreScannedUsingFluentScanner.cs | 2 -- .../Abstractions/DefaultStepTitleFactory.cs | 10 +++++++--- .../ExecutableAttribute/ExecutableAttribute.cs | 3 +-- 4 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/TestStack.BDDfy.Tests/Scanner/FluentScanner/StepTitleTests.cs b/src/TestStack.BDDfy.Tests/Scanner/FluentScanner/StepTitleTests.cs index 03480177..899a52f5 100644 --- a/src/TestStack.BDDfy.Tests/Scanner/FluentScanner/StepTitleTests.cs +++ b/src/TestStack.BDDfy.Tests/Scanner/FluentScanner/StepTitleTests.cs @@ -19,6 +19,7 @@ public void UseConfiguration_IncludeInputsInStepTitle() Configurator.StepTitleFactory.IncludeInputsInStepTitle = false; FooClass something = new(); var story = this + .Given(_=>something.Sub.GivenWithStepTitleAndArgument(1)) .When(_ => something.Sub.ActionWithArgument("foo")) .And(_ => something.Sub.ActionWithArgumentsDisabledInTitle("foo")) .And(_ => something.Sub.ActionWithTemplateTitleAndArguments("foo")) @@ -28,6 +29,7 @@ public void UseConfiguration_IncludeInputsInStepTitle() var actualTitles = story.Scenarios.Single().Steps.Select(s => s.Title).ToArray(); var expectedTitles = new[] { + "Given step title with 1 args", "When with arg", "And with arg", "And with foo arg", @@ -117,6 +119,10 @@ public void ActionWithArgumentsEnabledInTitle(string arg) public void ActionWithTemplateTitleAndArguments(string arg) { } + + [Given("step title with {0} args")] + public void GivenWithStepTitleAndArgument(int count) + { } } private string AMethodCall() diff --git a/src/TestStack.BDDfy.Tests/Scanner/FluentScanner/WhenStepsAreScannedUsingFluentScanner.cs b/src/TestStack.BDDfy.Tests/Scanner/FluentScanner/WhenStepsAreScannedUsingFluentScanner.cs index cddd0e74..032dda2a 100644 --- a/src/TestStack.BDDfy.Tests/Scanner/FluentScanner/WhenStepsAreScannedUsingFluentScanner.cs +++ b/src/TestStack.BDDfy.Tests/Scanner/FluentScanner/WhenStepsAreScannedUsingFluentScanner.cs @@ -265,8 +265,6 @@ Step AndInputsAreFormattedPropertlyInTheTitle ScenarioToBeScannedUsingFluentScanner.InputDateStepTitleTemplate, ScenarioToBeScannedUsingFluentScanner.InputDate); - var titles = _steps.Select(x=>x.Title.Trim()).ToArray(); - return _steps.Single(s => s.Title.Trim() == formattedTitle); } } diff --git a/src/TestStack.BDDfy/Abstractions/DefaultStepTitleFactory.cs b/src/TestStack.BDDfy/Abstractions/DefaultStepTitleFactory.cs index 847c34e9..3aacce02 100644 --- a/src/TestStack.BDDfy/Abstractions/DefaultStepTitleFactory.cs +++ b/src/TestStack.BDDfy/Abstractions/DefaultStepTitleFactory.cs @@ -22,11 +22,15 @@ string createTitle() var flatInputArray = inputArguments.Select(o => o.Value).FlattenArrays(); var name = methodInfo.Name; var titleAttribute = methodInfo.GetCustomAttribute(true); + var executableAttribute = methodInfo.GetCustomAttribute(true); + includeInputsInStepTitle ??= titleAttribute?.IncludeInputsInStepTitle ?? IncludeInputsInStepTitle; - if (titleAttribute is not null) + var titleTemplate = titleAttribute?.StepTitle ?? executableAttribute?.StepTitle; + + if (titleTemplate is not null) { - name = string.Format(titleAttribute.StepTitle, flatInputArray); + name = string.Format(titleTemplate, flatInputArray); } var stepTitle = AppendPrefix(Configurator.Humanizer.Humanize(name), stepPrefix); @@ -74,7 +78,7 @@ private static string AppendPrefix(string title, string stepPrefix) if (!title.StartsWith(stepPrefix, StringComparison.CurrentCultureIgnoreCase)) { if (title.Length == 0) return string.Format("{0} ", stepPrefix); - return string.Format("{0} {1}{2}", stepPrefix, title.Substring(0, 1).ToLower(), title.Substring(1)); + return string.Format("{0} {1}{2}", stepPrefix, title[..1].ToLower(), title[1..]); } return title; diff --git a/src/TestStack.BDDfy/Scanners/StepScanners/ExecutableAttribute/ExecutableAttribute.cs b/src/TestStack.BDDfy/Scanners/StepScanners/ExecutableAttribute/ExecutableAttribute.cs index dcc4c58d..67189c2f 100644 --- a/src/TestStack.BDDfy/Scanners/StepScanners/ExecutableAttribute/ExecutableAttribute.cs +++ b/src/TestStack.BDDfy/Scanners/StepScanners/ExecutableAttribute/ExecutableAttribute.cs @@ -1,11 +1,10 @@ using System; -using System.Diagnostics.CodeAnalysis; using TestStack.BDDfy.Annotations; namespace TestStack.BDDfy { /// - /// This attribute is marked with + /// This attribute is marked with /// so that any method decorated with [Executable] (or derived GWT attributes) /// is treated as "used implicitly" by code-analysis tools (ReSharper/Rider/etc). /// From 8828218bd000bc0e5f66c89fa58fcf4b47a99351 Mon Sep 17 00:00:00 2001 From: Gurpreet Singh Date: Tue, 26 May 2026 00:45:29 +0100 Subject: [PATCH 3/3] remove unused namespaces --- src/Samples/TestStack.BDDfy.Samples/CanRunAsyncSteps.cs | 1 - .../ExecutableAttribute/ExecutableAttributeStepScanner.cs | 1 - 2 files changed, 2 deletions(-) diff --git a/src/Samples/TestStack.BDDfy.Samples/CanRunAsyncSteps.cs b/src/Samples/TestStack.BDDfy.Samples/CanRunAsyncSteps.cs index 0d42f864..47516cc1 100644 --- a/src/Samples/TestStack.BDDfy.Samples/CanRunAsyncSteps.cs +++ b/src/Samples/TestStack.BDDfy.Samples/CanRunAsyncSteps.cs @@ -1,7 +1,6 @@ using System; using System.Threading.Tasks; using Shouldly; -using TestStack.BDDfy.Configuration; using Xunit; namespace TestStack.BDDfy.Samples diff --git a/src/TestStack.BDDfy/Scanners/StepScanners/ExecutableAttribute/ExecutableAttributeStepScanner.cs b/src/TestStack.BDDfy/Scanners/StepScanners/ExecutableAttribute/ExecutableAttributeStepScanner.cs index 3c4fc75d..957137ed 100644 --- a/src/TestStack.BDDfy/Scanners/StepScanners/ExecutableAttribute/ExecutableAttributeStepScanner.cs +++ b/src/TestStack.BDDfy/Scanners/StepScanners/ExecutableAttribute/ExecutableAttributeStepScanner.cs @@ -1,4 +1,3 @@ -using System; using System.Collections.Generic; using System.Linq; using System.Reflection;