Step Argument Transformations & Specflow Tables Tutorial:
Our previous Specflow tutorial briefed us all about Shared & Scoped Bindings, Hooks and Step Reuse in detail. Here in this tutorial, we will explore more about Step Argument Transformations in Specflow.
Feel free to read through our Complete Specflow Training Guide for Beginners for a clear understanding of the concept. Step Argument Transformation feature of Specflow, allows a user to provide custom transformation for the parameters supplied in the Steps.
It allows custom logic to be added to convert input parameters into a specific parameter. For Example, you can directly create a class object from the parameters and return the constructed object from the transformation function.
Another feature of Specflow that we will look at it is Specflow Tables which allow passing input data in tabular form with a single step and Table helpers can get it mapped directly to an Object instance as desired.
Watch the VIDEO:
Here is a video tutorial on Step Argument Transformations & Specflow tables:
Table of Contents:
Step Argument Transformations
To understand Argument transformations in a better way, let’s first try to figure out how exactly Specflow does match the parameters. As we’ve seen in our previous articles, for YouTube search example, we were passing the search term as a parameter for the scenario to execute.
The parameter matching usually happens through a regular expression and the matching regex results in setting the method parameter to the supplied search term in the step.
Let’s first try to understand what are the default supported conversions in Specflow and when argument transformations can be helpful.
Supported Conversions
Specflow does support a lot of conversions out of the box, by looking at the data type itself after regex match. It can automatically take care of conversions like – String, integer, GUID, Enums, etc.
Let’s see an Example for some of these below:
Scenario: Get Transactions in my account Given I have entered customer name as Test Customer And I have entered customer account id as 0f8fad5b-d9cb-469f-a165-70867728950e And I select sorting order as DESCENDING And I select number of transactions to be displayed as 25 Then I should see my account transactions
In the above code sample, we have highlighted different input types that we are passing in the Steps, and in the step implementations, these are getting converted to the respective data types.
Let’s see the step implementations for these below (for simplicity we have just did a console out for each of the steps to illustrate that argument supplied is automatically converted to the expected type):
[Given(@"I have entered customer name as (.*)")] public void GivenIHaveEnteredCustomerNameAsTestCustomer(String customerName) { Console.Out.WriteLine(customerName); } [Given(@"I have entered customer account id as (.*)")] public void GivenIHaveEnteredCustomerAccountIdAs(Guid accountId) { Console.Out.WriteLine(accountId.ToString()); } [Given(@"I select sorting order as (.*)")] public void GivenISelectSortingOrderAsAscending(SortOrder sortOrder) { Console.Out.WriteLine(sortOrder.ToString()); } [Then(@"I should see my account transactions")] public void ThenIShouldSeeMyAccountTransactions() { Console.Out.WriteLine("success!"); } [Given(@"I select number of transactions to be displayed as (.*)")] public void GivenISelectNumberOfTransactionsToBeDisplayedAs(int p0) { Console.Out.WriteLine(p0.ToString());
On executing the above scenario, the output prints all the values successfully by indicating that the auto conversion of arguments to the expected data types was successful.
This is how the output looks like:
Given I have entered customer name as Test Customer Test Customer -> done: SupportedSpecflowConversions.GivenIHaveEnteredCustomerNameAsTestCustomer("Test Customer") (0.0s) And I have entered customer account id as 0f8fad5b-d9cb-469f-a165-70867728950e 0f8fad5b-d9cb-469f-a165-70867728950e -> done: SupportedSpecflowConversions.GivenIHaveEnteredCustomerAccountIdAs(0f8fad5b-d9cb-469...) (0.0s) And I select sorting order as DESCENDING DESCENDING -> done: SupportedSpecflowConversions.GivenISelectSortingOrderAsAscending(DESCENDING) (0.0s) And I select number of transactions to be displayed as 25 25 -> done: SupportedSpecflowConversions.GivenISelectNumberOfTransactionsToBeDisplayedAs(25) (0.0s) Then I should see my account transactions success! -> done: SupportedSpecflowConversions.ThenIShouldSeeMyAccountTransactions() (0.0s)
Argument Transformations
Let’s see an example in action to understand this. Support, you have an application that converts the given time and converts it into minutes. Example: If the user input is 1 day – the output is – 1440, if user input is 1 day 2 hour 2 minutes, then the output should be 1562.
Now, it can be seen that to support different types of inputs, one will need to write different binding implementations depending on the type of inputs. For Example: For inputs having just daypart, there will be a separate step implementation, for inputs having a day, month part – there will be separate step implementation, etc.
Let’s see how this can be implemented through a single step implementation through Step Argument transformation and the provided input is simply converted into timestamp object and returned to the original step that is called the step transformation.
Think of it as a first level regex scan to your input which returns the partially transformed value to the calling step.
Look at the feature file having 3 different input variants, with a single transform by converting it into complete timespan object and returning back.
Scenario: Convert timestamp to minutes - variant 1 Given I have entered 50 days into the timestamp to minute converter When I press calculate Then the result should be 72000.00 on the screen Scenario: Convert timestamp to minutes - variant 2 Given I have entered 1 day, 2 hours, 3 minutes into the timestamp to minute converter When I press calculate Then the result should be 1563.00 on the screen Scenario: Convert timestamp to minutes - variant 3 Given I have entered 1 day, 1 hour, 1 minute, 30 seconds into the timestamp to minute converter When I press calculate Then the result should be 1501.50 on the screen
Look at the highlighted values in the above code example. All of these will be taken care of the exact same transformation and the end result will be a transformed TimeSpan input value that is sent back to the calling Specflow Step.
Let’s look at the implementation of the Transformation below:
[StepArgumentTransformation(@"(?:(\d*) day(?:s)?(?:, )?)?(?:(\d*) hour(?:s)?(?:, )?)?(?:(\d*) minute(?:s)?(?:, )?)?(?:(\d*) second(?:s)?(?:, )?)?")] public TimeSpan convertToTimeSpan(String days, String hours, String minutes, String seconds) { int daysValue; int hoursValue; int minutesValue; int secondsValue; int.TryParse(days, out daysValue); int.TryParse(hours, out hoursValue); int.TryParse(minutes, out minutesValue); int.TryParse(seconds, out secondsValue); return new TimeSpan(daysValue, hoursValue, minutesValue, secondsValue); }
For the framework to know that it is a transformation binding, StepArgumentTransformation Attribute needs to be added to the method implementing the Argument conversion.
The other important points to be noted regarding Argument conversions is:
#1) Step Argument transformations run for each matching step i.e. irrespective of the type of step i.e. whether it is Given, When or Then, Transformation will happen for each matching regex.
#2) Depending upon the return type of the transformed output, if the actual calling step does not have the matching return type for the input parameter, then transformation will not take place.
What this means is, suppose the calling step requires a transformed input but it has the mentioned timestamp of input as something which does not match the return type of the transformed method, then the regex match will be overridden and the conversion will not take place.
Let’s look at the implementation of calling the “Given” step:
private TimeSpan ts; [Given(@"I have entered (.*) into the timestamp to minute converter")] public void GivenIHaveEnteredDaysIntoTheTimestampToMinuteConverter(TimeSpan tsTransformed) { ts = tsTransformed; }
Look at the type of input parameter here i.e. its TimeSpan, which matches the type returned from the transformation step if this is changed to some other type. For instance String, then the argument conversion will not happen and the regex match will be overridden by the original Step implementation.
Specflow Tables
Specflow tables are a way to pass a list of the values to step implementation function. In our previous articles, we looked at the way to implement data-driven tests using Scenario outline and Examples. But that was to primarily execute the scenario with different inputs.
Here, in tables, it’s about passing all the data at once in tabular form to the step implementation which is supplying data.
For instance, consider an example where you are testing a Student Management System and in order to create a new Student object, you are requested to fill in a lot of details like first name, last name, age, year of birth, etc.
One way is to pass each of this information as a separate Step which will be essentially a lot of boilerplate code and in each step, you will end up updating the same object that needs to be tested. Another way can be building a complex regex and trying to pass all data in the same step but it’s quite error prone and flaky.
Tables come to our rescue here. All the student related input data can be sent into the same step implementation in a nice tabular way through the table feature of specflow.
Let’s see a code sample below for Feature and Step implementation:
Scenario: Pass data through Specflow tables for StudentInfo object Given I have entered following info for Student | FirstName | LastName | Age | YearOfBirth | | test | student | 20 | 1995 | When I press add Then i student should get added to database and entered info should be displayed on the screen
Table data is highlighted in the above Scenario Step.
Specflow does provide a lot of TableHelpers, which directly allow for useful features like creating an object instance from the user-supplied input data rather than parsing each field on your own.
Let’s see the step implementation below:
private StudentInfo studInfo; [Given(@"I have entered following info for Student")] public void GivenIHaveEnteredFollowingInfoForStudent(Table table) { // converting supplied input data directly to instance of StudentInfo object studInfo = table.CreateInstance<StudentInfo>(); }
Look at the highlighted section above. Here is just one small line of code, the entire StudentInfo object (which is a POCO containing the student data fields i.e. first name, last name, age, year of birth, etc)
Some other features/concepts related to Specflow tables are shown below:
#1) Tables can be horizontal or vertical. Vertical tables are more like key-value pairs and in the above scenario more like name-value mappings whereas horizontal tables contain all data for an object in a single row (just like we saw in our example).
#2) Vertical tables can be mapped to just a single .NET object whereas horizontal tables can also be mapped to a Set or Collection of Objects.
#3) Each field value in the table should be atomic as it will be mapped to a single corresponding field in the parsed object.
An important point to note here is that even if you auto-generate step bindings with the tabular data, the Specflow binding generator will automatically account for such input types and recognize it as a valid tabular data.
Conclusion
In this article, we tried to explain 2 important and handy concepts in Specflow.
The first step is the Step Argument transformations which allow custom type conversions for Specflow arguments to avoid boilerplate code (and enables the test script to look more modularized and logical) and the second feature we looked at is Specflow Tables which come in handy when you need to pass in a lot of fields/data in a single step in a user friendly tabular format.
In our upcoming tutorial, we will learn more about, how you can auto-generate beautiful documentation using Specflow in different formats using open source tools like Pickles which can serve an easy reference for all stakeholders of the project.