Moving testing focus from UI to REST API has in my opinion two main and obvious advantages – stability and speed. You can verify more test cases and there is less external factors that can disrupt test execution comparing to testing GUI.
There are many libraries you can use for automating REST API testing – in this article I will focus on using my favourite (so far) – RestSharp.
As an example I will use public API for exchange rates provided by Polish National Bank.
Let’s test Exchange Rate PLN-GBP from 22 NOV 2019 – based on API documentation request needed is http://api.nbp.pl/api/exchangerates/rates/a/gbp/2019-11-22
In RestSharp, we need only few lines of code to send this request and get response:
-
Create RestClient first with base URL
string url = "http://api.nbp.pl"; var restclient = new RestClient(url);
-
Prepare Request
var request = new RestRequest("/api/exchangerates/rates/A/GBP", Method.GET);
By default, GET method is used to it’s the same to write only
var request = new RestRequest("/api/exchangerates/rates/A/GBP");
-
Send request and get response
IResponse response = restclient.Execute(request);
That’s it!
To create request containing Headers and Body is also very easy and straightforward. Let’s use example POST message from petstore.swagger.io public API:
string url = "https://petstore.swagger.io";
var restclient = new RestClient(url);
var request = new RestRequest("/v2/pet", Method.POST);
request.AddHeader("Accept", "application/json");
request.AddJsonBody("{\"category\":{\"id\":0,\"name\":\"Dog\"},\"name\":\"Sccoby-Doo\",\"photoUrls\":[\"http://blog.qa-services.dev\"],\"tags\":[{\"id\":0,\"name\":\"QA-SERVICES\"}],\"status\":\"available\"}");
var response = restclient.Execute(request);
That was easy! However, sending requests and receiving responses is not testing yet – we need now verify if response is correct. There are many approaches and choosing one or another depends on many factors like:
-
time – do we have to quick check if API works ‘in general’ or we have time to prepare more sophisticated solution and verify whole responses of many test cases
-
importance – how important is accuracy of reponse data – do we have to verify only one or two parameters or checking whole response is crucial
-
access to database so we can generate expected response or we have to store expected responses in variables / files etc
Let’s start with the easiest approaches assuming we already have
IResponse object received from http://api.nbp.pl/api/exchangerates/rates/a/gbp/2019-11-22
- asserting StatusCode of response
using xUnit
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
using FluentAssertions – see related article
response.StatusCode
.Should()
.Be(HttpStatusCode.OK);
- asserting part of response
using xUnit
Assert.Contains("funt szterling", response.Content);
using FluentAssertions
response.Content
.Should()
.Contain("funt szterling");
- asserting full response – reference response
Notice: IMO it better to assert whole response unless there are some very important and reasonable reasons to not doing it 🙂
When we don’t have time we can save first response to file, verify it manually and then treat is as reference response. Following code is not perfect but it’s not only to present idea in simple way:)
[Theory]
[InlineData("GBP", "2019-10-22")]
[InlineData("USD", "2019-04-08")]
public void TestGet4_FullResponseData(string currency, string date)
{
string url = "http://api.nbp.pl";
string RefFile = $"C:\\temp\\{currency}_{date}.json";
var restclient = new RestClient(url);
var request = new RestRequest($"/api/exchangerates/rates/A/{currency}/{date}", Method.GET);
var response = restclient.Execute(request);
if (!File.Exists(RefFile))
File.WriteAllText(RefFile, response.Content);
Assert.Equal(File.ReadAllText(RefFile), response.Content);
}
Above test in xUnit implements two test cases for different currencies and dates. File for current pair of parameters is saved only if doesn’t exist. When the API changes along with expected response, it’s enough to delete file and run test to generate new reference file.
Notice: always remember to manually verify newly saved response!
Other ways for storing Reference Response are Database or code (e.g. static list).
- asserting full response – calculating expected response
If the algorithm is not very complicated and we have access to database, it’s good to implement generator of expected responses e.g. take data from request, get data from database and map this data to response object. If we then change text response to another object with the same structure, it will be easy to compare it!
First we need to implement model of response
JSON received from http://api.nbp.pl/api/exchangerates/rates/A/GBP/2019-11-21
{
"table": "A",
"currency": "funt szterling",
"code": "GBP",
"rates": [
{
"no": "225/A/NBP/2019",
"effectiveDate": "2019-11-21",
"mid": 5.0144
}
]
}
Corresponding model will look like this in C#:
using System.Collections.Generic;
namespace TestingRestApiDemo
{
public class ResponseModel
{
public string Table { get; set; }
public string Currency { get; set; }
public string Code { get; set; }
public IList Rates { get; set; }
public ResponseModel()
{
Rates = new List();
}
}
public class Rate
{
public string No { get; set; }
public string EffectiveDate { get; set; }
public double Mid { get; set; }
}
}
List of expected object can be now created in code (e.g. static list) or even better mapped from database.
public ResponseModel GetExpectedResponse(string currency, string date)
{
System.Data.DataRow dataRow = /* Get needed data from database for provided currency and date*/
return Map(dataRow);
}
private ResponseModel Map(System.Data.DataRow dataRow)
{
return new ResponseModel
{
Table = dataRow["table"].ToString(),
Currency = dataRow["currency"].ToString(),
Code = dataRow["code"].ToString(),
Rates = new List { new Rate()
{
No = dataRow["no"].ToString(),
EffectiveDate = dataRow["effectiveDate"].ToString(),
Mid = (double)dataRow["mid"]
}
}
};
}
Changing response from JSON string to corresponding object is called deserialization and can be easily done using Newtonsoft.Json package
var response = restclient.Execute(request);
var responseObject = JsonConvert.DeserializeObject(response.Content);
Assertion using FluentAssertions
ResponseModel expectedResponse = GetExpectedResponse(currency, date);
expectedResponse
.Should()
.BeEquivalentTo(responseObject);
After refactoring and extracting different parts to separate classes, it’s possible to get clean and easy to understand and maintenance tests
public class Tests
{
private ServiceDriver _serviceDriver;
public Tests()
{
_serviceDriver = new ServiceDriver(Settings.NbpEndpoint);
}
[Theory]
[InlineData("GBP", "2019-11-21")]
[InlineData("USD", "2019-04-08")]
public void TestGet7_Refactor(string currency, string date)
{
_serviceDriver
.GetExchangeRate(currency, date)
.Should()
.BeEquivalentTo(DataRepository.GetExpectedResponse(currency, date));
}
Source code can be found on GitHub repo.
-Kamil Marek