API Testing with .Net
How to tests API with RestSharp and RestAssured
To test APIs, I usually test with Postman. I also tried Bruno, and it is an interesting option but sometimes, some projects require comparing the API response with a database query, so if the QA team has experience with selenium C# it’s easier create API testing with .Net.
In one project, the team needed to test an importation using an API with a CSV file and then check if the information is stored on the database to ensure it was updated correctly. So I suggested C# API testing.
.NET includes the HttpClient for sending API requests, but RestSharp and RestAssured, which utilize BDD syntax (Given, When, Then), are excellent options for API testing.
To execute a Test on .NET, you have different options:
xUnit: is a free, open-source library that uses parallel testing by default, with [Fact] for tests and [Theory] for parametrized tests.
MSTest: Microsoft’s official testing framework, uses [TestClass] to enable the class for testing, [TestMethod] for asingle test without parameters, [DataTestMethod] for parameterized tests, [DataRow(1,2,3)] to include different data in the test on the [DataTestMethod]
NUnit: Is an open source testing framework migrated from JUnit after was rewritten with new features. Uses [Test] to define a test and [TestCase] to add the parameters for the test.
TUnit: A modern testing framework that focuses on the performance of the test execution. Is using [Test] to define the test and [Arguments(1,2,3)] to define the parameters to execute your tests.
API to test
For this article, I will use the API of my demo site to practice testing and development with C#, Angular, and tests with Playwright, Postman/Bruno, and now RestSharp/RestAssured with NUnit.
The API Swagger is https://effizienteauthdemo.azurewebsites.net/index.html. For this article, I will test the login API https://effizienteauthdemo.azurewebsites.net/api/Users/Login using the following body.
{
“Company”: “Demo”,
“UserName”: “Admin”,
“Password”: “Admin”
}Create the testing project
You can create a new testing project using either the Visual Studio Community version or VS Code. I will explain with VS Code.
You can install the C# Dev Kit extension for .NET projects.
Execute the following command to create a new NUnit project.
dotnet new nunit -o YourProjectName.TestsThis will add the NUnit packages and create a UnitTest.cs file.
public class Tests
{
[SetUp]
public void Setup()
{
}
[Test]
public void Test1()
{
Assert.Pass();
}
}The [SetUp] method is executed before each test, and to execute any Test, you need to add the [Test] attribute above the function.
JSON Schema
It’s a declarative language for describing the JSON response, including data types (such as string and boolean), constraints (e.g., indicating whether an attribute is required), and data structure. You can use the JSON Schema to validate the body of the API response.
For example, in this JSON, you can see that ‘name’ is a string, ‘age’ is a number between 0 and 120, and ‘email’ is a string.
{
“name”: “John Doe”,
“age”: 30,
“email”: “john@example.com”
}The JSON Schema is:
{
“type”: “object”,
“required”: [”name”, “age”],
“properties”: {
“name”: {
“type”: “string”,
“minLength”: 1
},
“age”: {
“type”: “integer”,
“minimum”: 0,
“maximum”: 120
},
“email”: {
“type”: “string”,
“format”: “email”
}
}
}For API testing, instead of validating each field and adding, for example, 5 validations, you can validate the schema with the JSON Schema. RestAssured includes this JSON schema validation.
I requested a Copilot tool to generate the JSON schema from the URL https://effizienteauthdemo.azurewebsites.net/swagger/v1/swagger.json
}.NET 9 includes a JSON Schema Exporter to get the JSON Schema
public static void SimpleExtraction()
{
JsonSerializerOptions options = JsonSerializerOptions.Default;
JsonNode schema = options.GetJsonSchemaAsNode(typeof(Person));
Console.WriteLine(schema.ToString());
//{
// “type”: [”object”, “null”],
// “properties”: {
// “Name”: { “type”: “string” },
// “Age”: { “type”: “integer” },
// “Address”: { “type”: [”string”, “null”], “default”: null }
// },
// “required”: [”Name”, “Age”]
//}
}
record Person(string Name, int Age, string? Address = null);Login Credentials
For the login credentials required by the API, I will create a class that contains the necessary JSON to log in.
public class LoginCredentials
{
public required string Company { get; set; }
public required string UserName { get; set; }
public required string Password { get; set; }
}It’s not a best practice to store passwords in code. .NET offers a secrets to store sensitive information.
Use secret and environment variables to store login credentials.
You can store user credentials in secrets, and for the pipeline, these will be environment secret variables. To allow .NET to read environment variables, you need to add the following NuGet packages:
dotnet add package Microsoft.Extensions.Configuration
dotnet add package Microsoft.Extensions.Configuration.EnvironmentVariables
dotnet add package Microsoft.Extensions.Configuration.UserSecretsInitialize the project to use secrets.
dotnet user-secrets initNow add the secrets variables.
dotnet user-secrets set “LoginCredentials:Company” “Demo”
dotnet user-secrets set “LoginCredentials:UserName” “Admin”
dotnet user-secrets set “LoginCredentials:Password” “Admin”To see the secrets values execute the next command:
dotnet user-secrets listTo enable .NET to read the .NET secrets and environment variables from a pipeline, create a new class TestConfiguration.cs with an IConfiguration variable that will read the values.
First, it will set up to read from secrets in case it is executed from a developer computer
using Microsoft.Extensions.Configuration;
using System.Reflection;
/// <summary>
/// Provides configuration settings for tests, supporting both User Secrets (local development)
/// and Environment Variables (CI/CD pipelines).
/// </summary>
public static class TestConfiguration
{
private static readonly IConfiguration Configuration;
/// <summary>
/// Static constructor that initializes the configuration providers.
/// Configuration sources are added in order of precedence (last added wins).
/// </summary>
static TestConfiguration()
{
Configuration = new ConfigurationBuilder()
.AddUserSecrets(Assembly.GetExecutingAssembly()) // Local development: dotnet user-secrets set "LoginCredentials:Company" "value"
.AddEnvironmentVariables() // CI/CD pipelines: export LoginCredentials__Company=value
.Build();
}
/// <summary>
/// Retrieves login credentials from configuration.
/// First checks environment variables, then falls back to user secrets.
/// </summary>
/// <returns>LoginCredentials object populated from configuration.</returns>
/// <exception cref=”InvalidOperationException”>Thrown when required credentials are missing.</exception>
public static LoginCredentials GetLoginCredentials()
{
var company = Configuration[”LoginCredentials:Company”];
var userName = Configuration[”LoginCredentials:UserName”];
var password = Configuration[”LoginCredentials:Password”];
if (string.IsNullOrEmpty(company) || string.IsNullOrEmpty(userName) || string.IsNullOrEmpty(password))
{
throw new InvalidOperationException(
"Login credentials are not configured. Please set user secrets using");
}
return new LoginCredentials
{
Company = company,
UserName = userName,
Password = password
};
}
}Restsharp
It is a .NET library that enables easy asynchronous or synchronous API calls to retrieve responses.
Execute the following command to install RestSharp.
dotnet add package RestSharpTo obtain the response, I typically create a class to handle the JSON response. For the login, the response is:
{
“Token”: “eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9zaWQiOiIyIiwiQ29tcGFueUlkIjoiMSIsIkF1eCI6IkNhRVVkdE1zaXNTOTNQMmZlbEhQLzVScGJzTG03L3A4blJaNHViVlhFeGkvVWlnS2lnVlUvZDJIWTFxamRuMWVoYlhvRWNOa0FFQjZkZnlWazJCKzBEZm5VaE1sblhyYjNMOEQzdz09IiwiVVJMIjoiaHR0cHM6Ly9lZmZpemllbnRlcmVzdC5henVyZXdlYnNpdGVzLm5ldC9hcGkvIiwiaHR0cDovL3NjaGVtYXMubWljcm9zb2Z0LmNvbS93cy8yMDA4LzA2L2lkZW50aXR5L2NsYWltcy9yb2xlIjpbIkFkbWluIiwiU3VwZXJ2aXNvciJdLCJleHAiOjE3NjEyNzg5NjksImlzcyI6Imh0dHBzOi8vZWZmaXppZW50ZWF1dGhkZW1vLmF6dXJld2Vic2l0ZXMubmV0IiwiYXVkIjoiaHR0cHM6Ly9lZmZpemllbnRlYXV0aGRlbW8uYXp1cmV3ZWJzaXRlcy5uZXQifQ.4V9RMK9kVWPy2xE_Sb03SUtbakey9gs5YfHgHLmIKaU”,
“TokenExpiration”: “2025-10-24T04:09:29.9846969+00:00”,
“UserId”: 2,
“Name”: “John Doe”,
“Email”: “john_doe@gmail.com”,
“RefreshToken”: “4hb9hOL9Q1J6sx9aIt1JhoWZOTgD78eBXr6hm6EuI94=”,
“Company”: “Demo”,
“CompanyKey”: 1
}Create a struct to convert the API response, and the test will validate that the token is not empty.
public struct UserSession
{
public required string Token { get; set; }
public DateTime TokenExpiration { get; set; }
public int UserId { get; set; }
public required string Name { get; set; }
public required string Email { get; set; }
public required string RefreshToken { get; set; }
public required string Company { get; set; }
public int CompanyKey { get; set; }
}Create a client.
var client = new RestClient(”https://effizienteauthdemo.azurewebsites.net”);Create the request for login is a POST request.
var request = new RestRequest(”api/Users/Login”, Method.Post);Get the login credentials from the TestConfiguration.
var loginCredentials = TestConfiguration.GetLoginCredentials();Add the login credentials to the body of the request.
request.AddJsonBody(loginCredentials);Execute the request and get the response as a UserSession object.
var response = await client.ExecutePostAsync<UserSession>(request);Check the status code is 200.
Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.OK), “Status code should be 200 OK for a successful login.”);Check that the response and token are not empty
Assert.That(response.Data, Is.Not.Null, “The response data should not be null.”);
Assert.That(response.Data.Token, Is.Not.Null.And.Not.Empty, “A token should be returned in the response.”);This is the full code
[Test]
public async Task Login_WithValidCredentials_ShouldReturnOkAndTokenAsync()
{
// 1. Create a new RestClient with the base URL of the API.
var client = new RestClient(”https://effizienteauthdemo.azurewebsites.net”);
// 2. Create a new RestRequest for the login endpoint.
var request = new RestRequest(”api/Users/Login”, Method.Post);
// 3. Get login credentials from environment variables.
var loginCredentials = TestConfiguration.GetLoginCredentials();
// 4. Add the login credentials to the request body as JSON.
request.AddJsonBody(loginCredentials);
// 5. Execute the request asynchronously and get the full response.
var response = await client.ExecutePostAsync<UserSession>(request);
// 6. Assert that the response status code is 200 (OK).
Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.OK), “Status code should be 200 OK for a successful login.”);
// 7. Assert that the response body is not null and contains a non-empty token.
Assert.That(response.Data, Is.Not.Null, “The response data should not be null.”);
Assert.That(response.Data.Token, Is.Not.Null.And.Not.Empty, “A token should be returned in the response.”);
}RestAssured
Primarily used on Java, Bas Dijkstra published RestAssured as an Open-Source library for .NET, utilizing the BDD (Given, When, Then) approach and including JSON Schema Validation.
Add the RestAssured nugget package.
dotnet add package RestAssured.NetAdd the RestAssured import
using static RestAssured.Dsl;Get the login credentials from the configuration that reads from .NET secrets or environment variables.
var credentials = TestConfiguration.GetLoginCredentials();Load the JSON schema.
string schemaPath = Path.Combine(AppContext.BaseDirectory, “Schemas”, “LoginResponseSchema.json”);
string jsonSchema = File.ReadAllText(schemaPath);Call the API with the BDD syntax and RestAssured format, and check the status code and JSON Schema.
Given()
.ContentType(”application/json”)
.Body(new
{
Company = credentials.Company,
UserName = credentials.UserName,
Password = credentials.Password
})
.When()
.Post(”https://effizienteauthdemo.azurewebsites.net/api/Users/Login”)
.Then()
.StatusCode(200)
.And()
.MatchesJsonSchema(jsonSchema);This is the final code
using NUnit.Framework;
using static RestAssured.Dsl;
public class Tests
{
[Test]
public void Login_WithRestAssuredNet_ShouldReturnOkAndValidateSchema()
{
// Get login credentials from configuration
var credentials = TestConfiguration.GetLoginCredentials();
// Read JSON schema from file
string schemaPath = Path.Combine(AppContext.BaseDirectory, “Schemas”, “LoginResponseSchema.json”);
string jsonSchema = File.ReadAllText(schemaPath);
// RestAssured.Net fluent API with built-in JSON schema validation
Given()
.ContentType(”application/json”)
.Body(new
{
Company = credentials.Company,
UserName = credentials.UserName,
Password = credentials.Password
})
.When()
.Post(”https://effizienteauthdemo.azurewebsites.net/api/Users/Login”)
.Then()
.StatusCode(200)
.And()
.MatchesJsonSchema(jsonSchema);
}
}Execute your test on GitHub Actions
You can check my articles related to GitHub Actions to understand more about CI/CD and GitHub Actions.
GitHub Actions: Part 1 - Run your playwright tests on GitHub
GitHub Actions: Part 2 - Schedule and showcase your Playwright tests in GitHub pages
GitHub Actions: Part 3 - Shard your playwright tests for blazing speed
To add your test on GitHub Actions, you need to create the yml file and set the login credentials as GitHub secrets.
On GitHub, add the environment for the user credentials as a secret. GitHub (Settings → Secrets and variables → Actions → New repository secret
LOGINCREDENTIALS__COMPANY: Demo
LOGINCREDENTIALS__USERNAME: Admin
LOGINCREDENTIALS__PASSWORD: AdminNote: Use double underscores (__) for environment variable format in GitHub Actions.
Create the next folders: .github/workflows
Create the yml file that will set up the .NET version (9.0), restore dependencies, build the project, and execute the tests.
name: Run Tests
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
workflow_dispatch:
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: ‘9.0.x’
- name: Restore dependencies
run: dotnet restore
- name: Build
run: dotnet build --no-restore
- name: Run tests
run: dotnet test --no-build --verbosity normal
env:
LoginCredentials__Company: ${{ secrets.LOGINCREDENTIALS__COMPANY }}
LoginCredentials__UserName: ${{ secrets.LOGINCREDENTIALS__USERNAME }}
LoginCredentials__Password: ${{ secrets.LOGINCREDENTIALS__PASSWORD }}You can check my complete example API_Testing
I made some refactors to create an API Helper in case of a Unit change to a new version, and included some breaking changes. You only need to change the API Helper.
Added company API testing that requires a JWT Token
Added Allure report and added to Github Pages with trends of the previous executions.
You can check my article related to the Allure report for playwright.
Enhancing Test Reporting: Integrating Allure with Playwright in Azure DevOps


