Creating our API Test Scenarios
Get Recent Tweets from our Home Timeline
Creating the SpecFlow Scenario
When creating our BDD scenarios, we want to keep them simple and write them in a declarative style, sticking simply to the business logic, rather than writing them in an imperative style. A great article on this can be found at http://itsadeliverything.com/declarative-vs-imperative-gherkin-scenarios-for-cucumber.
- Open up the ‘BaseApiScenarios.feature’ file and add the following test scenario. We can also add a SpecFlow tag of @Api either above the ‘Scenario’ (to apply to the specific scenario) or above the ‘Feature’ (to apply to all scenarios within the feature), which will set the BaseUri and OAuth for us before we run our test scenario/s…
@Api Feature: Test Twitter Tweets Scenario: 01. Get recent tweets from our Home Timeline Given I post a tweet of "Hello World! This is a test tweet." When I retrieve the resource of "/home_timeline.json" Then the latest tweet is my message of "Hello World. This is a test tweet."
You should notice that the ‘Given’, ‘When’ and ‘Then’ lines are highlighted. This is because they do not yet have any step definitions attached to them.
Creating the Undefined Step Definitions
- Right-click on any of the three scenario lines in the Feature file and select ‘Generate Step Definitions’
- Ensure all the steps are checked in the new window that appears
- Ensure the Class name is correct (e.g. ‘BaseApiScenariosSteps’) and click ‘Copy Methods to Clipboard’
- Open up ‘BaseApiScenariosSteps.cs’ step definition class file and paste in your undefined step definitions. It should look similar to below once done…
using TechTalk.SpecFlow; namespace ProductAutomation.Steps { [Binding] public sealed class BaseApiScenariosSteps { [Given(@"I post a tweet of ""(.*)""")] public void GivenIPostATweetOf(string p0) { ScenarioContext.Current.Pending(); } [When(@"I retrieve the response of the ""(.*)"" resource")] public void WhenIRetrieveTheResponseOfTheResource(string p0) { ScenarioContext.Current.Pending(); } [Then(@"the latest tweet is my message of ""(.*)""")] public void ThenTheLatestTweetIsMyMessageOf(string p0) { ScenarioContext.Current.Pending(); } } }
- Rename the string parameters to something more suitable in each of the undefined step definition methods…
using TechTalk.SpecFlow; namespace ProductAutomation.Steps { [Binding] public sealed class BaseApiScenariosSteps { [Given(@"I post a tweet of ""(.*)""")] public void GivenIPostATweetOf(string tweet) { ScenarioContext.Current.Pending(); } [When(@"I retrieve the response of the ""(.*)"" resource")] public void WhenIRetrieveTheResponseOfTheResource(string apiResource) { ScenarioContext.Current.Pending(); } [Then(@"the latest tweet is my message of ""(.*)""")] public void ThenTheLatestTweetIsMyMessageOf(string tweet) { ScenarioContext.Current.Pending(); } } }
Creating the Test Methods
PostTweet() Method
In this method, we will make a POST request to the ‘/update.json’ resource with a message we have passed in from our ‘Given’ scenario line. We will then execute that request in order to get a response.
- Open up the ‘BaseApiTests.cs’ class file in the ‘Apis’ namespace and add the following method…
public static void PostTweet(string message) { Request = new RestRequest("/update.json", Method.POST); Request.AddParameter("status", message, ParameterType.GetOrPost); Response = Client.Execute(Request); }
GetResponseOfResource() Method
In this method, we will again create a new RestRequest() and set its resource to the ‘apiResource’ string value we pass in from our ‘When’ scenario line.
- Open up the ‘BaseApiTests.cs’ class file again and add the following method…
public static void GetResponseOfResource(string apiResource) { Request = new RestRequest(); Request.Resource = apiResource; Response = Client.Execute(Request); }
GetResponse() Method
You will notice that we have some repeated lines of code between the ‘PostTweet()’ method and the ‘GetResponseOfResource()’ method. To try and follow DRY (Do not Repeat Yourself) principle, let’s create a private method for the Client to execute the Request, then we can simply call it in the methods we need to.
- Open up the ‘BaseApiTests.cs’ class file again and add the following private method…
private static void GetResponse() { Response = Client.Execute(Request); }
We can then edit the ‘PostTweet()’ and ‘GetResponseOfResource()’ methods to call the ‘GetResponse()’ private method…
public static void PostTweet(string message) { Request = new RestRequest("/update.json", Method.POST); Request.AddParameter("status", message, ParameterType.GetOrPost); GetResponse(); } public static void GetResponseOfResource(string apiResource) { Request = new RestRequest(); Request.Resource = apiResource; GetResponse(); }
The whole thing should look similar to below…
using RestSharp; using RestSharp.Authenticators; using static ProductAutomation.Utils.Settings.Settings; namespace ProductAutomation.Apis { public class BaseApiTests { public static RestClient Client; public static IRestRequest Request; public static IRestResponse Response; public static void SetBaseUriAndAuth() { Client = new RestClient(baseUrl); Client.Authenticator = AuthTwitter(); } private static OAuth1Authenticator AuthTwitter() { OAuth1Authenticator oAuth1Authenticator = OAuth1Authenticator.ForProtectedResource(consumerKey, consumerSecret, accessToken, accessTokenSecret); return oAuth1Authenticator; } public static void PostTweet(string message) { Request = new RestRequest("/update.json", Method.POST); Request.AddParameter("status", message, ParameterType.GetOrPost); GetResponse(); } public static void GetResponseOfResource(string apiResource) { Request = new RestRequest(); Request.Resource = apiResource; GetResponse(); } private static void GetResponse() { Response = Client.Execute(Request); } } }
Connecting the ‘Given’ and ‘When’ Step Definitions to the Test Methods
We will now map the undefined step definitions to the appropriate methods we have added to post a tweet and get the response of a resource.
- Open up the ‘BaseApiScenariosSteps.cs’ step definition class file and refactor the ‘Given’ step definition so it correctly posts a tweet with a string that we pass into the method…
[Given(@"I post a tweet of ""(.*)""")] public void GivenIPostATweetOf(string tweet) { BaseApiTests.PostTweet(tweet); }
Also ensure that the correct using directive was added so that it can find where our ‘BaseApiTests.cs’ class file is…
using ProductAutomation.Apis;
- Open up the ‘BaseApiScenariosSteps.cs’ step definition class file again and refactor the ‘When’ step definition so it correctly gets a response from a specific resource that we pass into the method…
[When(@"I retrieve the response of the ""(.*)"" resource")] public void WhenIRetrieveTheResponseOfTheResource(string apiResource) { BaseApiTests.GetResponseOfResource(apiResource); }
Models
Deserialization
In order to assert our tweet was posted successfully, we need to return a response from our ‘/home_timeline.json’ and assert that the value of the ‘text’ key is equal to the tweet that we posted. For us to be able to do this successfully, we need to use Deserialization and create a class and constructor for the API resource with properties for any keys in our response that we want to interact / do something with.
- Open up the ‘HomeTimeline.cs’ class file in the ‘Models’ nested namespace and edit it so the class is public, contains an empty constructor, and also contains a property for the ‘text’ key that we want to be able to interact with in our code. The whole thing should look similar to below…
namespace ProductAutomation.Apis.Models { public class HomeTimeline { public HomeTimeline() { } public string text { get; set; } } }
DeserialiseResponse<T>() Method
We will now add a method that uses a generic and will deserialise the JSON of any ‘Models’ class that we pass into it.
- Open up the ‘BaseApiTests.cs’ class file again and add the following private method, which we will use later in our Assert method for our final ‘Then’ scenario line…
private static T DeserialiseResponse<T>() { JsonDeserializer jsonDeserializer = new JsonDeserializer(); return jsonDeserializer.Deserialize<T>(Response); }
Ensure the correct using directives have also been added for the use of ‘JsonDeserializer’…
using RestSharp.Deserializers;
Adding the Final Method for Asserting our Tweet was Posted
We will now create our test method to assert that the ‘text’ key in the JSON response correctly has a value of the tweet/message we posted. Our Twitter Home Timeline returns a List of most recent tweets. Because we only care about the most recent tweet, it would be the one at index 0 that we want to assert.
- Open up the ‘BaseApiTests.cs’ class file again and add the following method…
public static void AssertTweetWasPosted(string tweet) { var result = DeserialiseResponse<List<HomeTimeline>>(); Assert.True(result[0].text == tweet); }
Ensure the correct using directive for the usage of NUnit asserts has been added too…
using NUnit.Framework;
The whole thing should look similar to below when done…
using System.Collections.Generic; using NUnit.Framework; using ProductAutomation.Apis.Models; using RestSharp; using RestSharp.Authenticators; using RestSharp.Deserializers; using static ProductAutomation.Utils.Settings.Settings; namespace RestAutomation.Apis { public class BaseApiTests { public static RestClient Client; public static IRestRequest Request; public static IRestResponse Response; public static void SetBaseUriAndAuth() { Client = new RestClient(baseUrl); Client.Authenticator = AuthTwitter(); } private static OAuth1Authenticator AuthTwitter() { OAuth1Authenticator oAuth1Authenticator = OAuth1Authenticator.ForProtectedResource(consumerKey, consumerSecret, accessToken, accessTokenSecret); return oAuth1Authenticator; } public static void PostTweet(string message) { Request = new RestRequest("/update.json", Method.POST); Request.AddParameter("status", message, ParameterType.GetOrPost); GetResponse(); } public static void GetResponseOfResource(string apiResource) { Request = new RestRequest(); Request.Resource = apiResource; GetResponse(); } private static void GetResponse() { Response = Client.Execute(Request); } private static T DeserialiseResponse<T>() { JsonDeserializer jsonDeserializer = new JsonDeserializer(); return jsonDeserializer.Deserialize<T>(Response); } public static void AssertTweetWasPosted(string tweet) { var result = DeserialiseResponse<List<HomeTimeline>>(); Assert.True(result[0].text == tweet); } } }
Connecting the Step Definition to the Assert Method
- Open up the ‘BaseApiScenariosSteps.cs’ step definition class file again, and refactor the ‘Then’ step definition so it correctly calls the Assert method we just made…
[Then(@"the latest tweet is my message of ""(.*)""")] public void ThenTheLatestTweetIsMyMessageOf(string tweet) { BaseApiTests.AssertTweetWasPosted(tweet); }
Go back to the Feature file and run the scenario. CONGRATULATIONS, IT SHOULD NOW PASS! If you also login to Twitter and check your Home Timeline, you should see your tweet has been posted successfully 😀