I have been wanting to show various aspects of API testing, so for the past few days, I have been creating an API so that I can have something local to demonstrate API testing. This post will showcase the API that will be tested in the new few posts.
As I have mentioned in previous posts, I am not an expert coder, but I have experience with Visual Studio and C#. I also use ChatGPT to speed up development and to get the design maybe right.
I like finances, so I thought creating an API for a budgeting app would be good. It’s not all in place or 100% what I want, but it’s ok for now.
API Model Classes
After doing some research on creating an API, I created my model classes. It’s pretty straightforward, and I have listed the Budget class below. A Budget can have a list of Categories. The other classes are Category, BudgetItem, and Transaction. Categories can have a list of BudetItems. BudgetItems can contain Transactions. All these objects have a few properties to get started with.
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace BudgetAPI.Models
{
public class Budget
{
[Key]
public int Id { get; set; }
[Required]
public int UserId { get; set; } // Foreign key for User
[MaxLength(50)]
public string? Name { get; set; }
[Required]
public Month Month { get; set; }
[Required]
public int Year { get; set; }
// Navigation property for Categories
public List<Category>? Categories { get; set; }
}
}
Controllers
For each of these model classes, there are corresponding API endpoints to handle CRUD operations. After a bit, I noticed I needed more than just the four operations, so I added more for each object.
All four of my Controller classes follow a similar structure to the code snippet below. They serve as the entry points for client requests, directing those requests to appropriate actions that interact with the database. Each Controller is equipped with a set of API endpoints that perform CRUD operations on the resources, represented by model classes.
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using BudgetAPI.Models;
using System.Collections.Generic;
namespace BudgetAPI.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class BudgetController : ControllerBase
{
private readonly BudgetHandler _dbHandler;
private readonly ILogger<BudgetController> _logger;
public BudgetController(IConfiguration configuration, ILogger<BudgetController> logger)
{
_dbHandler = new BudgetHandler(configuration);
_logger = logger;
}
/// <summary>
/// Gets all the budgets but not the categories.
/// This method needs to change to get budgets by user for a time period maybe
/// </summary>
/// <returns></returns>
[HttpGet]
public IActionResult Get()
{
List<Budget> budgets = _dbHandler.GetAllBudgets();
return Ok(budgets);
}
[HttpGet("ByBudgetId/{id}")]
public IActionResult GetBudget(int id)
{
Budget budget = _dbHandler.GetBudget(id);
if (budget != null)
{
budget.Categories = _dbHandler.GetCategoriesByBudgetId(id);
foreach(Category category in budget.Categories)
{
category.BudgetItems = _dbHandler.GetBudgetItemsByCategoryId(category.Id);
foreach(BudgetItem item in category.BudgetItems)
{
item.Transactions = _dbHandler.GetTransactionsByBudgetItemId(item.Id);
}
}
return Ok(budget);
}
else
{
return NotFound("Budget not found.");
}
}
...
Database Handler
The BudgetHandler
class is the data access class in my application and is responsible for all interactions with the database. It encapsulates the SQL queries and database operations required to fetch and manipulate Budget
objects. Methods like GetAllBudgets
and GetBudget
demonstrate how the handler fetches data, converting SQL results into model objects that can be easily consumed by the Controllers.
using BudgetAPI.Models;
using Microsoft.Extensions.Configuration;
using Microsoft.Data.Sqlite;
using System.Collections.Generic;
using System.Data;
public partial class BudgetHandler
{
private readonly IConfiguration _configuration;
public BudgetHandler(IConfiguration configuration)
{
_configuration = configuration;
}
public List<Budget> GetAllBudgets()
{
List<Budget> budgets = new List<Budget>();
string query = "SELECT * FROM Budgets";
string sqlDataSource = _configuration.GetConnectionString("DefaultConnection");
using (SqliteConnection myCon = new SqliteConnection(sqlDataSource))
{
myCon.Open();
using (SqliteCommand myCommand = new SqliteCommand(query, myCon))
{
using (SqliteDataReader myReader = myCommand.ExecuteReader())
{
while (myReader.Read())
{
Budget budget = new Budget
{
Id = myReader.GetInt32("Id"),
UserId = myReader.GetInt32("UserId"),
Name = myReader.IsDBNull(myReader.GetOrdinal("Name")) ? string.Empty : myReader.GetString("Name"),
Month = (Month)myReader.GetInt32("Month"),
Year = myReader.GetInt32("Year")
};
budgets.Add(budget);
}
}
}
}
return budgets;
}
public Budget GetBudget(int id)
{
Budget budget = null;
string query = "SELECT * FROM Budgets where Id = @Id";
string sqlDataSource = _configuration.GetConnectionString("DefaultConnection");
using (SqliteConnection myCon = new SqliteConnection(sqlDataSource))
{
myCon.Open();
using (SqliteCommand myCommand = new SqliteCommand(query, myCon))
{
myCommand.Parameters.AddWithValue("@Id", id);
using (SqliteDataReader myReader = myCommand.ExecuteReader())
{
if (myReader.Read())
{
budget = new Budget
{
Id = myReader.GetInt32("Id"),
UserId = myReader.GetInt32("UserId"),
Name = myReader.IsDBNull(myReader.GetOrdinal("Name")) ? string.Empty : myReader.GetString("Name"),
Month = (Month)myReader.GetInt32("Month"),
Year = myReader.GetInt32("Year")
};
}
}
}
}
return budget;
}
...
API Testing
Now that I have an API to test against, I can do what I’m good at. I’ll follow this post with some practical testing aspects when performing API Testing. In the next post, I’ll be using Postman to demonstrate how that tool can be used.