SpecFlow is a tool that allows the writing of business-readable tests that can then be automated in code. If you’re new to SpecFlow check out my Pluralsight course or the resources page to get up to speed before looking at these more advanced topics.
SpecFlow will attempt to perform some conversions of step arguments as specified in the documentation.
We can also tell SpecFlow to use our own custom conversions. Take the feature below:
Feature: SomeFeature
Scenario: Update User Password
Given The original user was created over 10 days ago
When I update the users password to "some password"
Then I should see the following errors
| ErrorCode | ErrorDescription |
| 442 | Out of date user |
| 28 | Changes were not saved |
From this we generate some basic step definitions:
[Given]
public void Given_The_original_user_was_created_over_DAYS_days_ago(int days)
{
}
[When]
public void When_I_update_the_users_password_to_NEWPASSWORD(string newPassword)
{
}
[Then]
public void Then_I_should_see_the_following_errors(Table table)
{
}
Automatically Parsing 10 Days Ago into a DateTime
In the Given step we get an int specifying how many days ago the user was created. As it’s the Given step we need to put the system into a state where the user creation date, for example held as a DateTime in the user database, is 10 days old. If this is just a single step then we can simply write the code to subtract a number of days from todays date in the step itself. Note: this kind of date based testing where we rely on “now” may prove brittle, imagine the a Given ran just before midnight, but the Then gets executed after midnight, i.e. the next day.
If we have a lot of instances where we use “n days ago” throughout our scenarios, we can tell SpecFlow to perform a custom conversion instead.
In the scenario above, we want to treat “10 days ago” as something special.
First we need to change the given step definition to the regex style (underscore style doesn’t seem to work at present with custom parameter conversion):
[Given(@"The original user was created over (.*) days ago")]
public void GivenTheOriginalUserWasCreatedOverDaysAgo(int p0)
{
}
Next we modify this to capture the entire phrase, not just the number (10) but the whole phrase: “10 days ago”:
[Given(@"The original user was created over (.* days ago)")]
public void GivenTheOriginalUserWasCreatedOverDaysAgo(int p0)
{
}
Now we’re capturing the whole phrase, we change the parameter to a DateTime:
[Given(@"The original user was created over (.* days ago)")]
public void GivenTheOriginalUserWasCreatedOverDaysAgo(DateTime userCreatedDate)
{
}
If we try to run the scenario now we get and error: “System.FormatException : The string was not recognized as a valid DateTime. There is an unknown word starting at index 3”. This is because SpecFlow is trying to perform an invalid conversion from the string “10 days ago” to a DateTime.
The final step is to tell SpecFlow to perform a custom conversion when it sees “10 days ago” (or any number of days ago) and needs to transform it into a DateTime instance.
To do this we use the [StepArgumentTransformation] attribute. We can use this on a method in the step definition class itself, or create a new class with the [Binding] attribute applied:
[Binding]
public class CustomTransforms
{
[StepArgumentTransformation(@"(\d+) days ago")]
public DateTime DaysAgoTransform(int daysAgo)
{
return DateTime.Today.AddDays(-daysAgo);
}
}
If we run the tests now they will execute as expected, and the value that SpecFlow passes to our Given step parameter “userCreatedDate” is a valid DateTime of ten days ago.
The [StepArgumentTransformation] attribute can be used without a regex parameter (to match anything), but in this example we’re capturing the digits “(/d+)” representing the number of days ago.
It’s important (and potentially confusing at first) to note that the regex in the [StepArgumentTransformation] is matching the entire string that we first captured in the given, i.e. “10 days ago”. From this initially captured string “10 days ago”, we drill down into the actual numeric information that we want, i.e. “10”. These captured digits “10” are then passed as an int to the daysAgo parameter for use in our conversion method DaysAgoTransform which returns a DateTime that SpecFlow passes on to our Given step parameter.
Converting a Table to a Strongly Typed Collection
For the Then step:
[Then]
public void Then_I_should_see_the_following_errors(Table table)
{
}
SpecFlow passes us a weakly typed Table object contain the rows/columns.
We can use the [StepArgumentTransformation] to define a translation to s strongly typed collection of Error objects:
public class Error
{
public int ErrorCode { get; set; }
public string ErrorDescription { get; set; }
}
We now change the Then to take a list of these Error objects:
[Then]
public void Then_I_should_see_the_following_errors(IEnumerable<Error> errors)
{
}
Finally we tell SpecFlow about our desired custom conversion:
[StepArgumentTransformation]
public IEnumerable<Error> ErrorsTransform(Table table)
{
return table.CreateSet<Error>();
}
This code uses the CreateSet method from the SpecFlow Assist Helpers to convert each row in the Table object into an Error instance which results in:
To learn more about step parameter conversion, check out my SpecFlow Tips and Tricks Pluralsight course.
You can start watching with a Pluralsight free trial.
SHARE: