Today, this question came up on the SpecFlow Google Group:
Assuming I would like to define in Gherkin the following:
1. When I send some argument xxx with parameter aaa and another parameter bbb
2. When I send some argument xxx with parameter aaa
And I would like to have only one reusable function, something like this:
[When(@"I send some argument (.*) with parameter (.*) and another parameter (.*)")]
public void foo(string arg, string paramA, string paramB)
{
// check if paramB is null and do something
}
I am aware of the table feature (pipe separated values) but I would like to stick with this text-alike syntax.
We've encountered this use case several times in the past (also avoiding the table syntax) and used to solve it by delegating the shorter version to the longer one but I decided to go see if I can find a more elegant solution.
Matching the steps
The first step at matching both steps was to simply match the first step. Since version 1.9 SpecFlow has this wonderful syntax highlighting in .feature files which helps identify unbound steps:
We can see, that our first pattern is too greedy and matches the second step but not the way we need.
Changing the regular expression for the first parameter to something more restrictive, allows us to restrict the match to only the first step (notice that the second step has been colored purple to notify us of the fact that there is no matching step definition, yet):
The regex ([^ ]*) we use here means that we match all characters that are not spaces 0 to n times, thus denying the match of the second step because of the space character following the argument aaa. Sometimes, though, you also need to match spaces in arguments and that's when we use a slightly modified version like this: "([^"]*)" which means: match a quote, then match everything but a quote 0 to n times and save this match as a reference, and then match another quote. In a verbatim string (prefixed by the @ sign) this will look like this:
Note, that now you'll have to enclose your spaced string value in quotes, though, but you can still use the same method to put that step attribute on.
Now, let's go for the second argument.
Using a .NET Optional Parameter
My first try was to add an optional parameter to the method we already have and provide it with a default argument like this:
Unfortunately, SpecFlow complains that for the first step with only one argument the matching method needs to have only one parameter. I thought that the compiler would generate two methods here, one with and one without the optional parameter so that at runtime it could pick the right one depending on which parameters were provided with a value. It turns out that this is not so. Seems that IL code for a method with optional parameters contains only one method, as well, as per this article:
Intermediate language for optional parameter method: IL
.method private hidebysig static void Method([opt] int32 'value', [opt] string name) cil managed
{
.param [1] = int32(1)
.param [2] = string('Perl')
.maxstack 8
L_0000: ldstr "value = {0}, name = {1}"
L_0005: ldarg.0
L_0006: box int32
L_000b: ldarg.1
L_000c: call void [mscorlib]System.Console::WriteLine(string, object, object)
L_0011: ret
}
That's why SpecFlow complains.
Solution: Use Two Methods
It looks like there is not direct solution to the problem that would require only a single method. The solution we employ seems to be all you can do about it, at least right now with SpecFlow 1.9. Which would be to use (at least) two separate methods, one of which delegates its execution to the other, more general one:
Happy spec'ing!