3

I am looking for a way to have a step definition such as:

Given a collection of numbers 1,2,3,4

and map that to a step definition with either a int[], List, or IEnumerable

the regex (\d+(,\d+)*) matches, but means I need two parameters.

At present I have

[Given(@"a collection of numbers (\d+(,\d+)*)")]
public void givencollectionofnumbers(string p0, string p1)
{
    //p0 is "1,2,3,4"
    //p1 is ",4"
}

I have a simple workarouns that is

[Given(@"a collection of numbers (.*)")]
public void givencollectionofnumbers(string p0)
{
    var numbers = p0.Split(',').Select(x => int.Parse(x));
}

but I would like to do this in a more elegant manner potentially changing the type of the numbers to doubles and also ensuring that the regex only maches lists of numbers.

I also would rather not use a table for this as it seems excessive for simple list of data

Can anyone help with this

MikeW
  • 1,568
  • 2
  • 19
  • 39
  • Do you want it to also match a string like `1, 2, 3, 4`? What happens if there is an alpha in there; can it just be ignored (e.g. `1, 2, 3, 4, a`)? – Mike Perrenoud May 20 '14 at 13:12
  • I only want to match lists of numbers, an alpha would simply not match the regex so yes could be ignored, I want the matched parameter to be type safe if possible. The whitespace is not so important, but yes adding a \s* in there would be a good idea probably. My problem is not the actual regex to match the patterm, but the mapping of this to the parameters of the step definition via specflow – MikeW May 21 '14 at 09:22
  • 1
    There is nothing wrong with your "workaround". I'm not sure how having a collection as the parameter is more elegant. Changing the parse to double is just as easy as a collection parameter and if the list contains non-numerics, the test will fail, which I would think would be the expectation. – Dave Rael May 25 '14 at 12:44

2 Answers2

5

I just resolve the same issue on my project: this will do the trick

((?:.,\d+)*(?:.\d+))

If you want to accept single int as well, use this instead:

((?:.,\d+)*(?:.+))

There are 2 problems with your proposition :

  • Specflow try to match it as 2 parameters, when you need only 1. But I was unable to find a clear explanation of why it did that in the documentation

  • you definitely need a StepArgumentTransformation to transform your input string in any enumerable

So your final step functions will look like that:

[Given(@"foo ((?:.,\d+)*(?:.+))")]
public void Foo(IEnumerable<int> ints)
{
}

[StepArgumentTransformation(@"((?:.,\d+)*(?:.+))")]
public static IEnumerable<int> ListIntTransform(string ints)
{
    return ints.Split(new[] { ',' }).Select(int.Parse);
}

And you receive an Enumerable int in your Foo function.

Hope it helps.

Ouarzy
  • 3,015
  • 1
  • 16
  • 19
  • That looks a good regex, but I do not see how that gives any advantage other than validating the format of the string, the parameter of the method still needs to be a string rather than an IEnumerable or (params) int[] as I would ideally like – MikeW Jun 03 '14 at 10:09
  • I updated my answer because I think I was not clear enough. This way you get an Enumerable int in your spec function. I guess you did not use the StepArgumentTransformation at first, but I am pretty sure this is the only way to achieve this. – Ouarzy Jun 03 '14 at 11:29
  • fantastic thank you! That is so much neater, used in one step difintion its overkill, but I have a lot of steps that need a list of ints to one step argument transformation for them all is great. – MikeW Jun 03 '14 at 15:09
  • (?x)((?:\d+,)*\d+)(?-x) might work better since (?x) says ignore spaces and \d forces you to only accept numbers. '10, 20' or '10,20' should both match here. – Brantley Blanchard Feb 25 '15 at 22:51
1

I ran into something similar and here is what we came up with. The key issue is that every '(' SpecFlow sees creates a new parameter. In this case, you simply needed to add a noncapture group, '(?:'.

My RegEx Cookbook

As Ouzrzy wrote in his answer, you can add a step arg transform to keep the signature simpler or do it in the step as I have documented below.

Sentences

Scenario: All of these sentences match below definition
    Then The quick red fox jumped over '10, 20, 30' lazy dogs
    Then the quick brown fox jumped over '10,20' lazy dogs
    Then the quick brown fox jumped over '1' lazy dog

Best practice: All parameters (including ints) should be surrounded by single quotes. This increases readability and decreases the likely hood of an ambiguous match.

Definition

[Then(@"(?i)t(?-i)he quick (?:brown|red) fox jumped over '(?x)((?:\d+,)*\d+)(?-x)' lazy dogs?")]
public void ThenTheQuickBrownFoxJumpedOverLazyDog(string countCsv)
{
    Console.WriteLine("sum of " + countCsv + " = ");
    var countCollection = countCsv.Split(',').Select(int.Parse);
    Console.WriteLine(countCollection.Sum());
}

Best Practice: Never keep the auto-defined parameter names. Your parameter name should be descriptive not only of its type (int, string, etc.) but also the type of data you would expect (count, filename, etc.).

Brantley Blanchard
  • 1,208
  • 3
  • 14
  • 23