DEV Community

Cover image for NUNit and C# - Tutorial to automate your API Tests from scratch
Alicia Marianne
Alicia Marianne

Posted on • Updated on

NUNit and C# - Tutorial to automate your API Tests from scratch

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.

Test Pyramid

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:

  {
    "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"
  },
Enter fullscreen mode Exit fullscreen mode
  • 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;
Enter fullscreen mode Exit fullscreen mode

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

TDD process

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()
    {

    }
}
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

Now we can do the act of the test:

RestResponse restResponse = client.Execute(restRequest);
Enter fullscreen mode Exit fullscreen mode

And finally, we can use this restResponse to make our validation:

    restResponse.Should().NotBeNull();
    restResponse.StatusCode.Should().Be(HttpStatusCode.BadRequest);
Enter fullscreen mode Exit fullscreen mode

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);
    }
}
Enter fullscreen mode Exit fullscreen mode

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);
    }
}
Enter fullscreen mode Exit fullscreen mode

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;
    }

}
Enter fullscreen mode Exit fullscreen mode

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);
    }
}
Enter fullscreen mode Exit fullscreen mode

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; }
}
Enter fullscreen mode Exit fullscreen mode

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;  
}
Enter fullscreen mode Exit fullscreen mode

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"  
    };  
}
Enter fullscreen mode Exit fullscreen mode

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();  
}
Enter fullscreen mode Exit fullscreen mode

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"  
    };  
}
Enter fullscreen mode Exit fullscreen mode

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;
    }
Enter fullscreen mode Exit fullscreen mode

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();
    }
Enter fullscreen mode Exit fullscreen mode

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;
    }
Enter fullscreen mode Exit fullscreen mode

The test class:

  [Test]
    public void DeleteABook()
    {
        RestResponse response = request.DeleteFakeApiRequest(15);
        response.StatusCode.Should().Be(HttpStatusCode.OK);
        response.Content.Should().NotBeNull();
    }
Enter fullscreen mode Exit fullscreen mode

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)

Collapse
 
cherryramatis profile image
Cherry Ramatis

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:

{
  "teste": "teste"
}
Enter fullscreen mode Exit fullscreen mode
Collapse
 
m4rri4nne profile image
Alicia Marianne

Thanks for the tip, i've updated using it <3

Collapse
 
ipazooki profile image
Mo

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.

Collapse
 
m4rri4nne profile image
Alicia Marianne

Thanks! I’ll try do without restsharp next time ❤️

Collapse
 
imadyou profile image
Imad Yousef

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🙂.

Collapse
 
m4rri4nne profile image
Alicia Marianne

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 🥲

Collapse
 
fernandoandrade profile image
Fernando Andrade • Edited

Amazing content, I going to use this as reference for when I implementing tests in my projects

Collapse
 
lliw profile image
William Rodrigues

Great post!

Collapse
 
igorsantos13 profile image
Igor Santos

Great article!

Collapse
 
ayodejii profile image
Isaac Ayodeji Ikusika

sweeeeet

Collapse
 
orionth1 profile image
Matheus Emanoel

Nicee

Collapse
 
zoldyzdk profile image
Hewerton Soares

So good content!

Collapse
 
larideoliiveira profile image
Larissa de Oliveira

C# it's my first stack, great content!

Collapse
 
jaywilkinson profile image
James Wilkinson • Edited

Great post, thanks for this!

Just a point though - since your tests are calling a physical web address and checking the response, wouldn't these be integration tests instead of unit tests?

If the website is down, the tests will fail - which wouldn't be a result of anything in the code, but an external system.

Collapse
 
m4rri4nne profile image
Alicia Marianne

I said in the article that is an integration test, NUnit is just the name of the framework.

Collapse
 
jaywilkinson profile image
James Wilkinson

Ah, you did indeed. 😳

Collapse
 
aminmansuri profile image
hidden_dude

I'm skeptical of unit tests that just test for nulls or empty.
Such things can be checked with asserts in the code.

What unit tests should be checking for is that if you write X to the API you get back X. If you ask it to calculate something with the data you gave, it should return the proper calculation.

In other words unit tests should exercise the logic of the API (which you can measure with code coverage tools) and not merely do asserts.

Asserts are better done in the code itself.

I worry many people just write useless unit tests that don't really TEST the application, they just ASSERT certain things about data.

Collapse
 
m4rri4nne profile image
Alicia Marianne • Edited

The purpose of the article is to help those who want to start test APIs using the stack, not to decide or make an analysis about if the tests will be delivering value. As a said in the article, the tests made were integration tests, in a real scenario, probably you’ll have more tests than those, like performance and data consistency, but this is just an example.

Collapse
 
aminmansuri profile image
hidden_dude

Fair enough

Collapse
 
aminmansuri profile image
hidden_dude

Too many simple tests just adds a lot of load to the program while providing little value. Tests need to be sneaky and really push the code to reveal it's bugs.