Janez Lukan
Janez Lukan

Reputation: 1449

How to properly indicate which StepArgumentTrasformation to use with step?

I'm trying to transform SpecFlow step arguments to the same return type in two different steps.

Here is the simplified feature file:

Feature: TransforMe

@UseTableToIntsTransform
Scenario: First
Given I have some condition
When I do something
Then the results should be
| Index |
| 1     |
| 2     |
| 3     |

@UseStringToIntsTransform
Scenario Outline: Second
Given I have some condition
When I do something
Then the results should contain <expectedResults>

Examples: 
| expectedResults |
| 0               |
| 0,3,5,7,10      |

What I'm trying to do is to transform both Table and string from scenarios in Then steps to return IEnumerable as step argument. I'm trying with Scope, attribute regex restrictions,... no success. Here are step definitions:

[Binding]
public class TransforMeSteps
{
    [Given(@"I have some condition")]
    public void GivenIHaveSomeCondition()
    { }

    [When(@"I do something")]
    public void WhenIDoSomething()
    { }

    [Then(@"the results should be")]
    [Scope(Scenario = "First", Tag = "UseTableToIntsTransform")]
    public void ThenTheResultsShouldBe(IEnumerable<int> results)
    {
        results.ToList().ForEach(x => Debug.WriteLine(x));
    }

    [Then(@"the results should contain (.*)")]
    [Scope(Scenario = "Second", Tag = "UseStringToIntsTransform")]
    public void ThenTheResultsShouldContain(IEnumerable<int> results)
    {
        results.ToList().ForEach(x => Debug.WriteLine(x));
    }
}

[Binding]
[Scope(Scenario = "First", Tag = "UseTableToIntsTransform")]
public class TableToIntsTransform
{
    [StepArgumentTransformation(@"the results should be")]
    public IEnumerable<int> TableToInts(Table intsTable)
    {
        return new List<int> { 1, 2, 3 };
    }
}

[Binding]
public class StringToIntsTransform
{
    [StepArgumentTransformation(@"the results should contain (.*)")]
    [Scope(Scenario = "Second", Tag = "UseStringToIntsTransform")]
    public IEnumerable<int> StringToInts(string integersString)
    {
        return new List<int> { 4, 5, 6 };
    }
}

For the "First" scenario, I get green test, but with warning: Multiple step transformation matches to the input (| Index | | 1 | | 2 | | 3 | , target type: [System.Collections.Generic.IEnumerable`1[System.Int32]]). We use the first.

For the "Second" scenario, there's InvalidCastException thrown: Invalid cast from 'System.String' to 'System.Collections.Generic.IEnumerable`1[[System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]'.

Each transformation works by itself with corresponding step definition. If I, for example, change the order of transform classes to have StringToIntsTransform first, then "Second" scenario's tests are green.

So how do I properly indicate or restrict scope to the right transformation?

I was hoping to have something like this as my final solution:

    [Then(@"the results should contain (.*)")]
    [Then(@"the results should be")]
    public void ThenTheResultsShouldContain(IEnumerable<int> results)
    {
        results.ToList().ForEach(x => Debug.WriteLine(x));
    }

So one step definition only.

Upvotes: 3

Views: 1421

Answers (1)

Sam Holder
Sam Holder

Reputation: 32946

This is probably not ideal, but what I would do is create a class to represent the objects I'm returning and use that instead here. This class might simply wrap an int, but being a different class will allow specflow to differentiate between the step transformations to use. So something along the lines of:

[Binding]
public class TransforMeSteps
{
    [Given(@"I have some condition")]
    public void GivenIHaveSomeCondition()
    { }

    [When(@"I do something")]
    public void WhenIDoSomething()
    { }

    [Then(@"the results should be")]
    [Scope(Scenario = "First", Tag = "UseTableToIntsTransform")]
    public void ThenTheResultsShouldBe(IEnumerable<Index> results)
    {
        results.ToList().ForEach(x => Debug.WriteLine(x));
    }

    [Then(@"the results should contain (.*)")]
    [Scope(Scenario = "Second", Tag = "UseStringToIntsTransform")]
    public void ThenTheResultsShouldContain(IEnumerable<ExpectedResult> results)
    {
        results.ToList().ForEach(x => Debug.WriteLine(x));
    }
}

[Binding]
[Scope(Scenario = "First", Tag = "UseTableToIntsTransform")]
public class TableToIntsTransform
{
    [StepArgumentTransformation(@"the results should be")]
    public IEnumerable<Index> TableToInts(Table intsTable)
    {
        return new List<Index> { 1, 2, 3 };
    }
}

[Binding]
public class StringToIntsTransform
{
    [StepArgumentTransformation(@"the results should contain (.*)")]
    [Scope(Scenario = "Second", Tag = "UseStringToIntsTransform")]
    public IEnumerable<ExpectedResult> StringToInts(string integersString)
    {
        return new List<ExpectedResult> { 4, 5, 6 };
    }
}

public class Index
{
     private int value;
     public Index(int value)
     { this.value=value;}

     public static implicit operator int(Index index)
     {
         return index.value;
     }

     public static implicit operator Index(int value)
     {
         return new Index(value);
     }
}

you might be able to get away with making the Index and ExpectedResult classes implicitly convertable to an int to make it more convenient to use.

Upvotes: 1

Related Questions