Did you ever try automate your APIs tests and don't know how to start it? This tutorial will help you to automate your API Test using NUNit and C# from scratch, since understanding the API that will be tested, the main concerns when testing a API, setup and how automate your tests.
Table of Contents
- Before automate
- Understanding the API that will be tested
- Test Scenarios
- Setup
- Automation of First Scenario - GET Method
- Create a test that will fail
- Make this test pass
- Refactor
- Automating the other scenarios - POST, PUT and DELETE methods
- POST method
- PUT method
- DELETE method
- Conclusions
Before automate
Before stating automating, we need to understand what is going to be tested and which type of tests we'll be doing.
There's a lot of methodologies used when you need to automate a project, for this tutorial, we'll use the test pyramid to determinate what kind of test we'll be automating.
Basically, the test pyramid is a framework that can help developers and QAs create high-quality software and help the team build more reliable test suite.
The test pyramid operate through three levels:
- Unit Tests: They are the base of the pyramid ad are responsible for test individual components or functionalities to validate if they work as expected in isolate conditions.
- Integration Tests: Fundamentally, they tests how a feature communicates with external dependencies.
- End to End: Tests to ensure if the entire application is working as expected.
For API tests, we'll be automating the second level of the pyramid, the integration tests. When testing this level, we need keep in mind that:
- This tests can run slower than unit tests, due integration with external dependencies
- We'll need an preproduction environment to run them.
Understanding the API that will be tested
For this tutorial, we'll use the FakeAPI, and the endpoint that returns Books.
About the API:
- Base url: "https://fakerestapi.azurewebsites.net"
- Body response format:
{
"id": 1,
"title": "Book 1",
"description": "Lorem nonumy iusto takimata ut sit ut consetetur erat sanctus sed vel ut labore nulla et consetetur sed nonumy. Adipiscing hendrerit gubergren elitr at no\n",
"pageCount": 100,
"excerpt": "Nonumy duo no. Clita invidunt hendrerit lorem ea sed ipsum. Nonummy labore ut at lorem feugiat erat sit dolor sit et velit. Dolore sit lorem diam sit ipsum at stet elit kasd et et.\nJusto te accusam amet ea aliquyam iriure at et ea nihil esse nibh volutpat eros et. Eu consequat exerci voluptua dolor nonumy erat invidunt consetetur vel takimata veniam at zzril kasd dolor amet diam. Lorem kasd ipsum. Quod justo minim takimata dolor dolore sanctus clita diam dolore duis voluptua nonumy nibh cum velit tempor no. Velit suscipit diam ea sanctus consetetur dignissim magna dolor.\n",
"publishDate": "2023-09-05T15:30:40.6574078+00:00"
},
- Authentication: None
- Available methods:
- Get all books
- Search for specific book
- Create a new book
- Update an book
- Delete a book
Tests scenarios
Now that we defined the level of the tests and understand about the API, we can start to write the tests. When testing API at integration level, we need to consider:
- The status code
- The body response content
- If the API is able to validate query parameters or body content
So, for this API, we'll automate the following tests:
Test | Expected Result |
---|---|
Search for Books | Status code 200 |
Search for Books | Valid body response |
Search for a Book | Status code 200 |
Create a Book | Status code 200 |
Update a Book | Status code 200 |
Delete a Book | Status code 200 |
Setup
To install the dependecies, we'll use the Nuget Package management, after create a project on your IDE(Visual Studio or Rider), go to:
- Visual Studio: Tools > Manage Nuget Packages > Manage Nuget Packages for Soluction
- Rider: Tools > Nuget > Manage Nuget Packages for Project And install the dependencies for the project.
Automation of First Scenario - GET Method
Create a new file, called FakeApiTests.cs, in this file, we'll write our tests.
After that, we'll need to import for this class, our dependencies:
using System.Net;
using FluentAssertions;
using RestSharp;
To help us to automate the tests, we'll use TDD method, the steps are:
- Create a test that fail
- Make this test pass
- Refactor
Create a test that will fail
A test that can return to us an error, would be: Call the GET API for all books and wait that status code is 400, not 200.
First, we'll create our test method:
using System.Net;
using System.Text.Json;
using FluentAssertions;
using RestSharp;
public class FakeApiTests
{
[Test]
public void SearchBooks()
{
}
}
The Test
attribute is one way of marking a method inside a TestFixture class as a test. It is normally used for simple (non-parameterized) tests but may also be applied to parameterized tests without causing any extra test cases to be generated. You can see more notations here.
When automating tests, we use the 3A's method:
- Arrange: setup all the information to do the request
- Act: make the request
- Assert: validate responses
Starting with the arrange, we need to inform to RestSharp what is going to be used on the request. First, we'll create the client, using RestClient class:
RestClient client = new RestClient(baseUrl);
After that, we'll pass the informations about the request, using RestRequest class. In this moment we must inform the url and adicional information about the request if is necessary:
RestRequest restRequest = new RestRequest(baseUrl, Method.Get);
Now we can do the act of the test:
RestResponse restResponse = client.Execute(restRequest);
And finally, we can use this restResponse to make our validation:
restResponse.Should().NotBeNull();
restResponse.StatusCode.Should().Be(HttpStatusCode.BadRequest);
So, for now, our test looks like this:
using System.Net;
using System.Text.Json;
using FluentAssertions;
using RestSharp;
public class FakeApiTests
{
[Test]
public void SearchBooks()
{
var baseUrl = "https://fakerestapi.azurewebsites.net/api/v1/Books";
RestClient client = new RestClient(baseUrl);
RestRequest restRequest = new RestRequest(baseUrl, Method.Get);
RestResponse restResponse = client.Execute(restRequest);
restResponse.Should().NotBeNull();
restResponse.StatusCode.Should().Be(HttpStatusCode.BadRequest);
}
}
Make this test pass
To make our test pass, we just need to update the assertion:
using System.Net;
using System.Text.Json;
using FluentAssertions;
using RestSharp;
public class FakeApiTests
{
[Test]
public void SearchBooks()
{
var baseUrl = "https://fakerestapi.azurewebsites.net/api/v1/Books";
RestClient client = new RestClient(baseUrl);
RestRequest restRequest = new RestRequest(baseUrl, Method.Get);
RestResponse restResponse = client.Execute(restRequest);
restResponse.Should().NotBeNull();
restResponse.StatusCode.Should().Be(HttpStatusCode.OK);
}
}
Refactor
Now, is time to do the refactor. The best way to do a refactor, is think what we can re-use during the tests. We know that we'll need to do request for each test, so, we can take this method out of our test class and create a new one, called "Requests.cs":
using RestSharp;
public class RequestClass
{
var baseUrl = "https://fakerestapi.azurewebsites.net/api/v1/Books";
public RestResponse GetFakeApiRequest()
{
RestClient client = new RestClient(baseUrl);
RestRequest restRequest = new RestRequest(baseUrl, Method.Get);
RestResponse restResponse = client.Execute(restRequest);
return restResponse;
}
}
This new method will return our restResponse, and when updating the Test Class, we need to instance this class before use it:
using System.Net;
using System.Text.Json;
using FluentAssertions;
using RestSharp;
public class FakeApiTests
{
RequestClass request = new RequestClass();
[Test]
public void SearchBooks()
{
RestResponse response = request.GetFakeApiRequest();
restResponse.Should().NotBeNull();
restResponse.StatusCode.Should().Be(HttpStatusCode.OK);
}
}
After that, our test pass and we can reproduce the same steps for the other methods.
Automating the other scenarios - POST, PUT and DELETE methods
POST method
When creating a book, we'll use the POST method, for it, we need to pass a body request.
First, we'll create a new class called FakeApiEntities.cs, in this class, we'll create a object with the properties of body request/response.
Why use it? If the API in the future change, is going to be more easy to make changes.
_
using System.Text.Json.Serialization;
public class FakeApiEntities
{
[JsonPropertyName("id")]
public int? Id { get; set; }
[JsonPropertyName("title")]
public string Title { get; set; }
[JsonPropertyName("description")]
public string Description { get; set; }
[JsonPropertyName("pageCount")]
public int PageCount { get; set; }
[JsonPropertyName("excerpt")]
public string Excerpt { get; set; }
[JsonPropertyName("publishDate")]
public string PublishDate { get; set; }
}
Next, is create in the Request.cs class, the new method that will make the request:
public RestResponse PostFakeApiRequest()
{
RestClient client = new RestClient(baseUrl);
var body = BuildBodyRequest();
RestRequest restRequest = new RestRequest(baseUrl, Method.Post);
restRequest.AddBody(body, ContentType.Json);
RestResponse restResponse = client.Execute(restRequest);
return restResponse;
}
The method BuildBodyRequest that is called to generate the body request, we use the FakeApiEntities class to generate a new object:
public static FakeApiEntities BuildBodyRequest()
{
return new FakeApiEntities
{
Id = 100,
Title = "Test Book",
Description = "Mussum Ipsum, cacilds vidis litro abertis. Quem num gosta di mim que vai caçá sua turmis!",
Excerpt = "uem num gosta di mim que vai caçá sua turmis!",
PageCount = 100,
PublishDate = "2023-09-03T13:50:32.6884665+00:00"
};
}
The test method in Test.cs file will be:
[Test]
public void CreateABook()
{
RestResponse response = request.PostFakeApiRequest();
response.StatusCode.Should().Be(HttpStatusCode.OK);
response.Content.Should().NotBeNull();
var bodyContent = JsonSerializer.Deserialize<FakeApiEntities>(response.Content);
bodyContent.Id.Should().NotBeNull();
bodyContent.Description.Should().NotBeNull();
bodyContent.Title.Should().NotBeNull();
}
To validate the content of the body response, I deserialized the content of the response to make the assertions.
PUT method
For the PUT method, we'll basically use the same steps, but, there's some differences.
First, we'll change the method responsible to generate the body request. The Endpoint to update books use the same body, but, we need to pass the same Id in the body request that is sent in the url. That's why we'll make this parameter not mandatory and use ternary operator to make sure that the other tests that use this body don't fail:
public static FakeApiEntities BuildBodyRequest(int? id=null)
{
return new FakeApiEntities
{
Id = id ?? 100,
Title = "Test Book",
Description = "Mussum Ipsum, cacilds vidis litro abertis. Quem num gosta di mim que vai caçá sua turmis!",
Excerpt = "uem num gosta di mim que vai caçá sua turmis!",
PageCount = 100,
PublishDate = "2023-09-03T13:50:32.6884665+00:00"
};
}
For the method responsible to make the request, we'll do the changes on the url used by RestRequest class to accept the Id of the book that will be updated:
public RestResponse PutFakeApiRequest(int id)
{
RestClient client = new RestClient(baseUrl);
var body = BuildBodyRequest(id);
RestRequest restRequest = new RestRequest( $"{baseUrl}/{id}", Method.Put);
restRequest.AddBody(body, ContentType.Json);
RestResponse restResponse = client.Execute(restRequest);
return restResponse;
}
The test class:
[Test]
public void UpdateABook()
{
RestResponse response = request.PutFakeApiRequest(15);
response.StatusCode.Should().Be(HttpStatusCode.OK);
response.Content.Should().NotBeNull();
var bodyContent = JsonSerializer.Deserialize<FakeApiEntities>(response.Content);
bodyContent.Id.Should().NotBeNull();
bodyContent.Id.Should().Be(15);
bodyContent.Description.Should().NotBeNull();
bodyContent.Title.Should().NotBeNull();
}
DELETE method
The DELETE method, is similar of GET method, we'll allow this method receive and ID and change the method called by RestRequest class:
public RestResponse DeleteFakeApiRequest(int id)
{
RestClient client = new RestClient(baseUrl);
var body = BuildBodyRequest(id);
RestRequest restRequest = new RestRequest( $"{baseUrl}/{id}", Method.Delete);
restRequest.AddBody(body, ContentType.Json);
RestResponse restResponse = client.Execute(restRequest);
return restResponse;
}
The test class:
[Test]
public void DeleteABook()
{
RestResponse response = request.DeleteFakeApiRequest(15);
response.StatusCode.Should().Be(HttpStatusCode.OK);
response.Content.Should().NotBeNull();
}
Conclusions
This is a tutorial where I share all the knowledge that i've get learning NUnit and C# using the documentation of the framework and experimenting this in my work, you can see the full project here . I hope this content is useful for you and for any question, just reach me out!
Bisous, à bientot 💅🏼
Top comments (20)
Awesome content! I don't know anything about c# but your didactics really helped create a pleasure experience
One quick tip if you allow me:
You can insert the language after the codeblock symbol to enable syntax highlighting like so:
Thanks for the tip, i've updated using it <3
Although your approach to testing API was great, I wouldn't use RestSharp and would rather write my own HTTP client. However, your approach was completely robust.
Thanks for your great post.
Thanks! I’ll try do without restsharp next time ❤️
Great content! I like it. It's beneficial and easy to understand.
I apologize for being intrusive, but I couldn't help but notice that the FakeRestAPI's response StatusCode are not following the best Rest API practices. The Post method should return 201 (Created), the Delete and Update/PUT methods should return 204 (NoContent)
I appreciate the informative article you've written. Thank you🙂.
Thanks for the information. I didn’t create the API, I just used and create the tests based on swagger documentation. For sure, they not use the best practices for status code 🥲
Amazing content, I going to use this as reference for when I implementing tests in my projects
C# it's my first stack, great content!
Great post!
Great article!
sweeeeet
Nicee
So good content!