Developing and Testing a Multi-Environment ASP.NET Core Application with SQL Server
Introduction
Running the same code across environments demands clear configuration boundaries, safe database migrations, and repeatable tests. In this comprehensive step-by-step guide, you’ll develop and configure an ASP.NET Core Web API project from scratch to work consistently in Development, QA, and Production using ASP.NET Core, EF Core, and SQL Server. At the end of this guide, you’ll have a production-ready HRM (Human Resource Management) API with automated testing workflows for multiple environments.
Why it is important?
- Environment parity reduces “works on my machine” issues.
- Safer deployments with gated migrations and smoke tests.
- Clean separation of secrets and settings for Dev/QA/Prod.
- Faster troubleshooting with predictable diagnostics and seed data.
- Production-ready architecture with proper testing infrastructure.
Tools and Technology Used
- .NET 9 SDK
- ASP.NET Core Web API
- C#
- Entity Framework Core 9 (SQL Server)
- SQL Server (LocalDB, Express, or full version)
- xUnit (testing framework)
- PowerShell (automation scripts)
- Visual Studio 2022 or VS Code
Implementation:
Step 1: Prepare Development Environment
Option A — Local SQL Server Installation: Install SQL Server, or use LocalDB. Ensure SQL Server is accessible with your credentials.
Option B — Docker SQL Server (Windows PowerShell): If you don’t have SQL Server installed, use Docker to run a SQL Server container:
# Pull and run SQL Server 2022 in Docker
docker pull mcr.microsoft.com/mssql/server:2022-latest
docker run -e "ACCEPT_EULA=Y" -e "SA_PASSWORD=YourPassword123!" -p 1433:1433 --name sql2022 -d mcr.microsoft.com/mssql/server:2022-latest
# Verify connection
sqlcmd -S localhost -U sa -P "YourPassword123!" -Q "SELECT @@VERSION"
Verify Prerequisites:
# Check .NET 9 SDK
dotnet --version
# Expected: 9.0.x
# Test SQL Server connection
sqlcmd -S localhost -E -Q "SELECT @@VERSION"
# Check PowerShell version
$PSVersionTable.PSVersion
Step 2: Create Project Structure from Scratch
- Create the solution and project structure using the following commands:
# Create solution structure
mkdir multi-env-app
cd multi-env-app
mkdir src, doc
cd src
mkdir HRM
cd HRM
# Create solution and Web API project
dotnet new sln -n HRM
dotnet new webapi -n HRM.API --framework net9.0
dotnet sln add HRM.API/HRM.API.csproj
# Navigate to API project and install packages
cd HRM.API
dotnet add package Microsoft.EntityFrameworkCore.SqlServer --version 9.0.0
dotnet add package Microsoft.EntityFrameworkCore.Tools --version 9.0.0
dotnet add package Microsoft.EntityFrameworkCore.Design --version 9.0.0
dotnet add package Microsoft.AspNetCore.OpenApi --version 9.0.0
dotnet add package Swashbuckle.AspNetCore --version 6.8.1
# Create project folders
mkdir Controllers, Models, DTOs, Services, Data, Migrations
- You can use visual studio to create the solution and project structure if you prefer a GUI.
Project Structure Overview:
HRM/
├── HRM.sln # Solution file
├── HRM.API/ # Main Web API project
│ ├── Controllers/ # API endpoints (DepartmentsController, EmployeesController)
│ ├── Services/ # Business logic services and interfaces
│ ├── Data/ # Entity Framework DbContext
│ ├── Models/ # Entity models (Department, Employee)
│ ├── DTOs/ # Data transfer objects with validation
│ ├── Migrations/ # EF Core database migrations
│ ├── Properties/ # Launch settings with multiple profiles
│ ├── appsettings.json # Production configuration (base settings)
│ ├── appsettings.Development.json # Development environment settings
│ ├── appsettings.QA.json # QA environment settings
│ ├── Program.cs # Application entry point
│ └── HRM.API.csproj # Project file with dependencies
└── HRM.Tests/ # Test project (created later)
Step 3: Create Data Models and Database Context
Create Entity Models:
Create Models/Department.cs
:
using System.ComponentModel.DataAnnotations;
namespace HRM.API.Models
{
public class Department
{
public int DepartmentId { get; set; }
[Required]
[StringLength(100)]
public string Name { get; set; } = string.Empty;
[StringLength(500)]
public string? Description { get; set; }
public bool IsActive { get; set; } = true;
public DateTime CreatedDate { get; set; } = DateTime.UtcNow;
public DateTime? UpdatedDate { get; set; }
// Navigation property
public virtual ICollection<Employee> Employees { get; set; } = new List<Employee>();
}
}
Create Models/Employee.cs
:
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace HRM.API.Models
{
public class Employee
{
public int EmployeeId { get; set; }
[Required]
[StringLength(100)]
public string FirstName { get; set; } = string.Empty;
[Required]
[StringLength(100)]
public string LastName { get; set; } = string.Empty;
[Required]
[EmailAddress]
[StringLength(255)]
public string Email { get; set; } = string.Empty;
[StringLength(20)]
public string? PhoneNumber { get; set; }
[Required]
public DateTime HireDate { get; set; }
[Required]
[Column(TypeName = "decimal(10,2)")]
public decimal Salary { get; set; }
[Required]
public int DepartmentId { get; set; }
public DateTime CreatedDate { get; set; } = DateTime.UtcNow;
public DateTime? UpdatedDate { get; set; }
// Navigation property
public virtual Department Department { get; set; } = null!;
}
}
Create Database Context with Seed Data:
Create Data/HrmDbContext.cs
:
using Microsoft.EntityFrameworkCore;
using HRM.API.Models;
namespace HRM.API.Data
{
public class HrmDbContext : DbContext
{
public HrmDbContext(DbContextOptions<HrmDbContext> options) : base(options)
{
}
public DbSet<Employee> Employees { get; set; }
public DbSet<Department> Departments { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
// Configure relationships
modelBuilder.Entity<Employee>()
.HasOne(e => e.Department)
.WithMany(d => d.Employees)
.HasForeignKey(e => e.DepartmentId)
.OnDelete(DeleteBehavior.Restrict);
// Configure unique constraints
modelBuilder.Entity<Employee>()
.HasIndex(e => e.Email)
.IsUnique();
modelBuilder.Entity<Department>()
.HasIndex(d => d.Name)
.IsUnique();
// Seed sample data for testing
SeedData(modelBuilder);
}
private void SeedData(ModelBuilder modelBuilder)
{
// Seed Departments
modelBuilder.Entity<Department>().HasData(
new Department { DepartmentId = 1, Name = "Human Resources", Description = "Manages employee relations, recruitment, and benefits", IsActive = true, CreatedDate = new DateTime(2024, 1, 1, 0, 0, 0, DateTimeKind.Utc) },
new Department { DepartmentId = 2, Name = "Information Technology", Description = "Handles all technology infrastructure and software development", IsActive = true, CreatedDate = new DateTime(2024, 1, 1, 0, 0, 0, DateTimeKind.Utc) },
new Department { DepartmentId = 3, Name = "Finance", Description = "Manages company finances, budgeting, and accounting", IsActive = true, CreatedDate = new DateTime(2024, 1, 1, 0, 0, 0, DateTimeKind.Utc) },
new Department { DepartmentId = 4, Name = "Marketing", Description = "Handles marketing campaigns, branding, and customer outreach", IsActive = true, CreatedDate = new DateTime(2024, 1, 1, 0, 0, 0, DateTimeKind.Utc) },
new Department { DepartmentId = 5, Name = "Sales", Description = "Manages customer relationships and sales processes", IsActive = true, CreatedDate = new DateTime(2024, 1, 1, 0, 0, 0, DateTimeKind.Utc) }
);
// Seed Employees
modelBuilder.Entity<Employee>().HasData(
new Employee { EmployeeId = 1, FirstName = "Sarah", LastName = "Johnson", Email = "sarah.johnson@company.com", PhoneNumber = "+1-555-0101", HireDate = new DateTime(2023, 3, 15), Salary = 75000m, DepartmentId = 1, CreatedDate = new DateTime(2024, 1, 1, 0, 0, 0, DateTimeKind.Utc) },
new Employee { EmployeeId = 2, FirstName = "Michael", LastName = "Chen", Email = "michael.chen@company.com", PhoneNumber = "+1-555-0102", HireDate = new DateTime(2022, 8, 22), Salary = 95000m, DepartmentId = 2, CreatedDate = new DateTime(2024, 1, 1, 0, 0, 0, DateTimeKind.Utc) },
new Employee { EmployeeId = 3, FirstName = "Emily", LastName = "Davis", Email = "emily.davis@company.com", PhoneNumber = "+1-555-0103", HireDate = new DateTime(2023, 1, 10), Salary = 82000m, DepartmentId = 3, CreatedDate = new DateTime(2024, 1, 1, 0, 0, 0, DateTimeKind.Utc) }
);
}
}
}
Step 4: Configure Multi-Environment Settings
ASP.NET Core loads appsettings.json
plus appsettings.{Environment}.json
. Create environment-specific configuration files:
Create appsettings.json
(Production base):
{
"ConnectionStrings": {
"DefaultConnection": "Server=prod-server;Database=HRM_Prod;User Id=hrm_user;Password=secure_prod_password;TrustServerCertificate=true;Encrypt=true;"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning",
"Microsoft.EntityFrameworkCore": "Warning"
}
},
"AllowedHosts": "*",
"ApiSettings": {
"Version": "v1",
"Title": "HRM API - Production",
"EnableSwagger": false,
"EnableDetailedErrors": false
}
}
Create appsettings.Development.json
:
{
"ConnectionStrings": {
"DefaultConnection": "Server=localhost;Database=HRM_DEV;User Id=sa;Password=YourPassword123!;TrustServerCertificate=true;"
},
"Logging": {
"LogLevel": {
"Default": "Debug",
"Microsoft.AspNetCore": "Information",
"Microsoft.EntityFrameworkCore": "Information",
"Microsoft.EntityFrameworkCore.Database.Command": "Information"
}
},
"ApiSettings": {
"Version": "v1",
"Title": "HRM API - Development",
"EnableSwagger": true,
"EnableDetailedErrors": true
}
}
Create appsettings.QA.json
:
{
"ConnectionStrings": {
"DefaultConnection": "Server=localhost;Database=HRM_QA;User Id=sa;Password=YourPassword123!;TrustServerCertificate=true;"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Information",
"Microsoft.EntityFrameworkCore": "Information"
}
},
"ApiSettings": {
"Version": "v1",
"Title": "HRM API - QA Environment",
"EnableSwagger": true,
"EnableDetailedErrors": true
}
}
Configure Multiple Launch Profiles:
Update Properties/launchSettings.json
:
{
"profiles": {
"development": {
"commandName": "Project",
"applicationUrl": "https://localhost:7285;http://localhost:5152",
"launchBrowser": true,
"launchUrl": "swagger",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"qa": {
"commandName": "Project",
"applicationUrl": "https://localhost:7286;http://localhost:5153",
"launchBrowser": true,
"launchUrl": "swagger",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "QA"
}
},
"production": {
"commandName": "Project",
"applicationUrl": "https://localhost:7287;http://localhost:5154",
"launchBrowser": false,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Production"
}
}
}
}
Set environment when running:
- Run the following command in PowerShell to set the environment variable:
$env:ASPNETCORE_ENVIRONMENT = "Development" # or "QA", "Production"
Step 5: Create Business Logic Services
Create service interfaces and implementations (key files):
Create Services/IDepartmentService.cs
:
using HRM.API.DTOs;
namespace HRM.API.Services
{
public interface IDepartmentService
{
Task<IEnumerable<DepartmentDto>> GetAllDepartmentsAsync();
Task<DepartmentDto?> GetDepartmentByIdAsync(int id);
Task<DepartmentDto> CreateDepartmentAsync(CreateDepartmentDto createDepartmentDto);
Task<bool> DeleteDepartmentAsync(int id);
Task<bool> DepartmentExistsAsync(int id);
}
}
Create Services/DepartmentService.cs
(implementing business logic with EF Core operations).
Step 6: Create API Controllers
Create Controllers/DepartmentsController.cs
:
using Microsoft.AspNetCore.Mvc;
using HRM.API.DTOs;
using HRM.API.Services;
namespace HRM.API.Controllers
{
[ApiController]
[Route("api/[controller]")]
[Produces("application/json")]
public class DepartmentsController : ControllerBase
{
private readonly IDepartmentService _departmentService;
public DepartmentsController(IDepartmentService departmentService)
{
_departmentService = departmentService;
}
[HttpGet]
public async Task<ActionResult<IEnumerable<DepartmentDto>>> GetDepartments()
{
var departments = await _departmentService.GetAllDepartmentsAsync();
return Ok(departments);
}
[HttpGet("{id}")]
public async Task<ActionResult<DepartmentDto>> GetDepartment(int id)
{
var department = await _departmentService.GetDepartmentByIdAsync(id);
if (department == null)
return NotFound($"Department with ID {id} not found.");
return Ok(department);
}
[HttpPost]
public async Task<ActionResult<DepartmentDto>> CreateDepartment(CreateDepartmentDto createDepartmentDto)
{
if (!ModelState.IsValid)
return BadRequest(ModelState);
var department = await _departmentService.CreateDepartmentAsync(createDepartmentDto);
return CreatedAtAction(nameof(GetDepartment), new { id = department.DepartmentId }, department);
}
[HttpDelete("{id}")]
public async Task<IActionResult> DeleteDepartment(int id)
{
var result = await _departmentService.DeleteDepartmentAsync(id);
if (!result)
return NotFound($"Department with ID {id} not found.");
return NoContent();
}
}
}
Step 7: Configure Program.cs for Multi-Environment Support
Update Program.cs
:
using Microsoft.EntityFrameworkCore;
using HRM.API.Data;
using HRM.API.Services;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container
builder.Services.AddControllers();
// Configure Entity Framework with environment-specific connection string
builder.Services.AddDbContext<HrmDbContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));
// Register business services for dependency injection
builder.Services.AddScoped<IDepartmentService, DepartmentService>();
builder.Services.AddScoped<IEmployeeService, EmployeeService>();
// Configure API documentation
builder.Services.AddEndpointsApiExplorer();
// Conditionally add Swagger based on environment configuration
var apiSettings = builder.Configuration.GetSection("ApiSettings");
if (apiSettings.GetValue<bool>("EnableSwagger"))
{
builder.Services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new()
{
Title = apiSettings.GetValue<string>("Title"),
Version = apiSettings.GetValue<string>("Version")
});
});
}
var app = builder.Build();
// Configure the HTTP request pipeline based on environment
if (app.Environment.IsDevelopment() || app.Environment.EnvironmentName == "QA")
{
if (apiSettings.GetValue<bool>("EnableSwagger"))
{
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", apiSettings.GetValue<string>("Title"));
});
}
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
// Ensure database is created (for development/demo purposes)
using (var scope = app.Services.CreateScope())
{
var context = scope.ServiceProvider.GetRequiredService<HrmDbContext>();
context.Database.EnsureCreated();
}
app.Run();
Note: EnsureCreated()
is used for demo purposes. For production, use Database.Migrate()
and controlled migrations.
Step 8: Create and Apply Database Migrations
Navigate to the API project and create/apply migrations for each environment:
cd d:/Projects/GitHub/rnd/projects/in-progress/multi-env-app/src/HRM/HRM.API
# Create initial migration
dotnet ef migrations add InitialCreate
# Apply migration to Development database
$env:ASPNETCORE_ENVIRONMENT = "Development"
dotnet ef database update
# Apply migration to QA database
$env:ASPNETCORE_ENVIRONMENT = "QA"
dotnet ef database update
# Apply migration to Production database (when ready)
$env:ASPNETCORE_ENVIRONMENT = "Production"
dotnet ef database update
When models change, add new migrations:
$env:ASPNETCORE_ENVIRONMENT = "Development"
dotnet ef migrations add AddNewField
dotnet ef database update
Verify Database Creation: After running migrations, you should have three separate databases:
HRM_DEV
(Development)HRM_QA
(QA)HRM_Prod
(Production)
Each database contains the seeded data from HrmDbContext
, providing consistent test data across environments.
Step 9: Run and Test the API Across Environments
cd d:/Projects/GitHub/rnd/projects/in-progress/multi-env-app/src/HRM/HRM.API
# Run in Development with Swagger UI
$env:ASPNETCORE_ENVIRONMENT = "Development"
dotnet run --launch-profile development
# Access: http://localhost:5152/swagger
# Run in QA environment
$env:ASPNETCORE_ENVIRONMENT = "QA"
dotnet run --launch-profile qa
# Access: http://localhost:5153/swagger
# Run in Production mode (no Swagger)
$env:ASPNETCORE_ENVIRONMENT = "Production"
dotnet run --launch-profile production
# Access: http://localhost:5154/api/departments
Environment-Specific Features:
- Development: Swagger UI enabled, detailed logging, debug information
- QA: Swagger UI enabled, moderate logging, similar to production but with testing features
- Production: Swagger disabled, minimal logging, optimized for performance
Run the application for different environments using Visual Studio
- Select the desired profile (development, qa, production) from the debug dropdown and run.
Step 10: API Testing and Validation
Manual Testing with PowerShell:
# Test Development environment
$env:ASPNETCORE_ENVIRONMENT = "Development"
# Start the API in another terminal: dotnet run --launch-profile development
# Get all departments
Invoke-RestMethod -Method GET -Uri "http://localhost:5152/api/departments" | ConvertTo-Json
# Get all employees
Invoke-RestMethod -Method GET -Uri "http://localhost:5152/api/employees" | ConvertTo-Json
# Create a new department
$newDept = @{
name = "Research & Development"
description = "Innovation and product development"
isActive = $true
} | ConvertTo-Json
Invoke-RestMethod -Method POST -Uri "http://localhost:5152/api/departments" -ContentType "application/json" -Body $newDept
# Create a new employee
$newEmployee = @{
firstName = "John"
lastName = "Doe"
email = "john.doe@company.com"
phoneNumber = "+1-555-1234"
hireDate = "2024-10-12T00:00:00"
salary = 75000.00
departmentId = 2
} | ConvertTo-Json
Invoke-RestMethod -Method POST -Uri "http://localhost:5152/api/employees" -ContentType "application/json" -Body $newEmployee
Using VS Code REST Client:
Create test-requests.http
in the HRM.API project:
### Get all departments
GET http://localhost:5152/api/departments
Accept: application/json
### Get all employees
GET http://localhost:5152/api/employees
Accept: application/json
### Create new department
POST http://localhost:5152/api/departments
Content-Type: application/json
{
"name": "Quality Assurance",
"description": "Software testing and quality control",
"isActive": true
}
### Create new employee
POST http://localhost:5152/api/employees
Content-Type: application/json
{
"firstName": "Jane",
"lastName": "Smith",
"email": "jane.smith@company.com",
"phoneNumber": "+1-555-5678",
"hireDate": "2024-10-12T00:00:00",
"salary": 80000.00,
"departmentId": 1
}
Step 11: Create Automated Testing Infrastructure
Create Test Project:
# Navigate to HRM solution directory
cd .. # from HRM.API to HRM
# Create test project
dotnet new xunit -n HRM.Tests
dotnet sln add HRM.Tests/HRM.Tests.csproj
# Add test packages and project reference
cd HRM.Tests
dotnet add package Microsoft.AspNetCore.Mvc.Testing --version 9.0.0
dotnet add package Microsoft.EntityFrameworkCore.InMemory --version 9.0.0
dotnet add package FluentAssertions --version 6.12.0
dotnet add reference ../HRM.API/HRM.API.csproj
Create Integration Test Infrastructure:
Create HRM.Tests/TestWebApplicationFactory.cs
:
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using HRM.API.Data;
namespace HRM.Tests
{
public class TestWebApplicationFactory<TStartup> : WebApplicationFactory<TStartup> where TStartup : class
{
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureServices(services =>
{
// Remove the real database context
var descriptor = services.SingleOrDefault(
d => d.ServiceType == typeof(DbContextOptions<HrmDbContext>));
if (descriptor != null)
{
services.Remove(descriptor);
}
// Add in-memory database for testing
services.AddDbContext<HrmDbContext>(options =>
{
options.UseInMemoryDatabase("InMemoryDbForTesting");
});
// Build the service provider and create the database
var sp = services.BuildServiceProvider();
using (var scope = sp.CreateScope())
{
var scopedServices = scope.ServiceProvider;
var db = scopedServices.GetRequiredService<HrmDbContext>();
var logger = scopedServices.GetRequiredService<ILogger<TestWebApplicationFactory<TStartup>>>();
// Ensure the database is created
db.Database.EnsureCreated();
try
{
// Seed test data if needed
SeedTestData(db);
}
catch (Exception ex)
{
logger.LogError(ex, "An error occurred seeding the database with test messages. Error: {Message}", ex.Message);
}
}
});
builder.UseEnvironment("Testing");
}
private static void SeedTestData(HrmDbContext context)
{
// Add additional test data if needed beyond what's in OnModelCreating
if (!context.Departments.Any())
{
context.Departments.AddRange(
new HRM.API.Models.Department { Name = "Test HR", Description = "Test HR Department", IsActive = true },
new HRM.API.Models.Department { Name = "Test IT", Description = "Test IT Department", IsActive = true }
);
context.SaveChanges();
}
}
}
}
Create Department Integration Tests:
Create HRM.Tests/IntegrationTests/DepartmentControllerTests.cs
:
using System.Net.Http.Json;
using System.Net;
using System.Text;
using FluentAssertions;
using HRM.API;
using HRM.API.DTOs;
using Newtonsoft.Json;
using Xunit;
namespace HRM.Tests.IntegrationTests
{
public class DepartmentControllerTests : IClassFixture<TestWebApplicationFactory<Program>>
{
private readonly HttpClient _client;
private readonly TestWebApplicationFactory<Program> _factory;
public DepartmentControllerTests(TestWebApplicationFactory<Program> factory)
{
_factory = factory;
_client = _factory.CreateClient();
}
[Fact]
public async Task GetDepartments_ReturnsSuccessAndCorrectContentType()
{
// Act
var response = await _client.GetAsync("/api/departments");
// Assert
response.StatusCode.Should().Be(HttpStatusCode.OK);
response.Content.Headers.ContentType?.ToString()
.Should().Contain("application/json");
}
[Fact]
public async Task GetDepartments_ReturnsListOfDepartments()
{
// Act
var response = await _client.GetAsync("/api/departments");
var content = await response.Content.ReadAsStringAsync();
var departments = JsonConvert.DeserializeObject<List<DepartmentDto>>(content);
// Assert
response.StatusCode.Should().Be(HttpStatusCode.OK);
departments.Should().NotBeNull();
departments.Should().HaveCountGreaterThan(0);
departments!.First().Name.Should().NotBeNullOrEmpty();
}
[Fact]
public async Task GetDepartment_WithValidId_ReturnsSuccessAndCorrectDepartment()
{
// Arrange - First get all departments to find a valid ID
var allDepartmentsResponse = await _client.GetAsync("/api/departments");
var allDepartmentsContent = await allDepartmentsResponse.Content.ReadAsStringAsync();
var allDepartments = JsonConvert.DeserializeObject<List<DepartmentDto>>(allDepartmentsContent);
var firstDepartment = allDepartments!.First();
// Act
var response = await _client.GetAsync($"/api/departments/{firstDepartment.DepartmentId}");
var content = await response.Content.ReadAsStringAsync();
var department = JsonConvert.DeserializeObject<DepartmentDto>(content);
// Assert
response.StatusCode.Should().Be(HttpStatusCode.OK);
department.Should().NotBeNull();
department!.DepartmentId.Should().Be(firstDepartment.DepartmentId);
department.Name.Should().Be(firstDepartment.Name);
}
[Fact]
public async Task GetDepartment_WithInvalidId_ReturnsNotFound()
{
// Act
var response = await _client.GetAsync("/api/departments/99999");
// Assert
response.StatusCode.Should().Be(HttpStatusCode.NotFound);
}
[Fact]
public async Task CreateDepartment_WithValidData_ReturnsCreated()
{
// Arrange
var newDepartment = new CreateDepartmentDto
{
Name = $"Test Department {Guid.NewGuid()}",
Description = "Integration test department",
IsActive = true
};
var json = JsonConvert.SerializeObject(newDepartment);
var content = new StringContent(json, Encoding.UTF8, "application/json");
// Act
var response = await _client.PostAsync("/api/departments", content);
var responseContent = await response.Content.ReadAsStringAsync();
var createdDepartment = JsonConvert.DeserializeObject<DepartmentDto>(responseContent);
// Assert
response.StatusCode.Should().Be(HttpStatusCode.Created);
createdDepartment.Should().NotBeNull();
createdDepartment!.Name.Should().Be(newDepartment.Name);
createdDepartment.Description.Should().Be(newDepartment.Description);
createdDepartment.IsActive.Should().Be(newDepartment.IsActive);
createdDepartment.DepartmentId.Should().BeGreaterThan(0);
}
[Fact]
public async Task CreateDepartment_WithInvalidData_ReturnsBadRequest()
{
// Arrange - Create department with missing required field
var invalidDepartment = new { Description = "Missing name field" };
var json = JsonConvert.SerializeObject(invalidDepartment);
var content = new StringContent(json, Encoding.UTF8, "application/json");
// Act
var response = await _client.PostAsync("/api/departments", content);
// Assert
response.StatusCode.Should().Be(HttpStatusCode.BadRequest);
}
[Fact]
public async Task DeleteDepartment_WithValidId_ReturnsNoContent()
{
// Arrange - First create a department to delete
var newDepartment = new CreateDepartmentDto
{
Name = $"Department to Delete {Guid.NewGuid()}",
Description = "Will be deleted",
IsActive = true
};
var json = JsonConvert.SerializeObject(newDepartment);
var content = new StringContent(json, Encoding.UTF8, "application/json");
var createResponse = await _client.PostAsync("/api/departments", content);
var createContent = await createResponse.Content.ReadAsStringAsync();
var createdDepartment = JsonConvert.DeserializeObject<DepartmentDto>(createContent);
// Act
var deleteResponse = await _client.DeleteAsync($"/api/departments/{createdDepartment!.DepartmentId}");
// Assert
deleteResponse.StatusCode.Should().Be(HttpStatusCode.NoContent);
// Verify it's actually deleted
var getResponse = await _client.GetAsync($"/api/departments/{createdDepartment.DepartmentId}");
getResponse.StatusCode.Should().Be(HttpStatusCode.NotFound);
}
[Fact]
public async Task DeleteDepartment_WithInvalidId_ReturnsNotFound()
{
// Act
var response = await _client.DeleteAsync("/api/departments/99999");
// Assert
response.StatusCode.Should().Be(HttpStatusCode.NotFound);
}
}
}
Create Employee Integration Tests:
Create HRM.Tests/IntegrationTests/EmployeeControllerTests.cs
:
using System.Net.Http.Json;
using System.Net;
using System.Text;
using FluentAssertions;
using HRM.API;
using HRM.API.DTOs;
using Newtonsoft.Json;
using Xunit;
namespace HRM.Tests.IntegrationTests
{
public class EmployeeControllerTests : IClassFixture<TestWebApplicationFactory<Program>>
{
private readonly HttpClient _client;
private readonly TestWebApplicationFactory<Program> _factory;
public EmployeeControllerTests(TestWebApplicationFactory<Program> factory)
{
_factory = factory;
_client = _factory.CreateClient();
}
[Fact]
public async Task GetEmployees_ReturnsSuccessAndCorrectContentType()
{
// Act
var response = await _client.GetAsync("/api/employees");
// Assert
response.StatusCode.Should().Be(HttpStatusCode.OK);
response.Content.Headers.ContentType?.ToString()
.Should().Contain("application/json");
}
[Fact]
public async Task GetEmployees_ReturnsListOfEmployees()
{
// Act
var response = await _client.GetAsync("/api/employees");
var content = await response.Content.ReadAsStringAsync();
var employees = JsonConvert.DeserializeObject<List<EmployeeDto>>(content);
// Assert
response.StatusCode.Should().Be(HttpStatusCode.OK);
employees.Should().NotBeNull();
employees.Should().HaveCountGreaterThan(0);
employees!.First().FirstName.Should().NotBeNullOrEmpty();
}
[Fact]
public async Task CreateEmployee_WithValidData_ReturnsCreated()
{
// Arrange - Get a valid department ID first
var departmentsResponse = await _client.GetAsync("/api/departments");
var departmentsContent = await departmentsResponse.Content.ReadAsStringAsync();
var departments = JsonConvert.DeserializeObject<List<DepartmentDto>>(departmentsContent);
var validDepartmentId = departments!.First().DepartmentId;
var newEmployee = new CreateEmployeeDto
{
FirstName = "Integration",
LastName = "Test",
Email = $"integration.test.{Guid.NewGuid()}@company.com",
PhoneNumber = "+1-555-TEST",
HireDate = DateTime.UtcNow,
Salary = 75000m,
DepartmentId = validDepartmentId
};
var json = JsonConvert.SerializeObject(newEmployee);
var content = new StringContent(json, Encoding.UTF8, "application/json");
// Act
var response = await _client.PostAsync("/api/employees", content);
var responseContent = await response.Content.ReadAsStringAsync();
var createdEmployee = JsonConvert.DeserializeObject<EmployeeDto>(responseContent);
// Assert
response.StatusCode.Should().Be(HttpStatusCode.Created);
createdEmployee.Should().NotBeNull();
createdEmployee!.FirstName.Should().Be(newEmployee.FirstName);
createdEmployee.LastName.Should().Be(newEmployee.LastName);
createdEmployee.Email.Should().Be(newEmployee.Email);
createdEmployee.EmployeeId.Should().BeGreaterThan(0);
}
[Fact]
public async Task UpdateEmployee_WithValidData_ReturnsSuccess()
{
// Arrange - First create an employee
var departmentsResponse = await _client.GetAsync("/api/departments");
var departmentsContent = await departmentsResponse.Content.ReadAsStringAsync();
var departments = JsonConvert.DeserializeObject<List<DepartmentDto>>(departmentsContent);
var validDepartmentId = departments!.First().DepartmentId;
var newEmployee = new CreateEmployeeDto
{
FirstName = "Original",
LastName = "Name",
Email = $"original.{Guid.NewGuid()}@company.com",
PhoneNumber = "+1-555-ORIG",
HireDate = DateTime.UtcNow,
Salary = 70000m,
DepartmentId = validDepartmentId
};
var createJson = JsonConvert.SerializeObject(newEmployee);
var createContent = new StringContent(createJson, Encoding.UTF8, "application/json");
var createResponse = await _client.PostAsync("/api/employees", createContent);
var createResponseContent = await createResponse.Content.ReadAsStringAsync();
var createdEmployee = JsonConvert.DeserializeObject<EmployeeDto>(createResponseContent);
// Update the employee
var updateEmployee = new UpdateEmployeeDto
{
FirstName = "Updated",
LastName = "Name",
Email = createdEmployee!.Email, // Keep same email
PhoneNumber = "+1-555-UPDT",
HireDate = createdEmployee.HireDate,
Salary = 80000m,
DepartmentId = validDepartmentId
};
var updateJson = JsonConvert.SerializeObject(updateEmployee);
var updateContent = new StringContent(updateJson, Encoding.UTF8, "application/json");
// Act
var response = await _client.PutAsync($"/api/employees/{createdEmployee.EmployeeId}", updateContent);
var responseContent = await response.Content.ReadAsStringAsync();
var updatedEmployee = JsonConvert.DeserializeObject<EmployeeDto>(responseContent);
// Assert
response.StatusCode.Should().Be(HttpStatusCode.OK);
updatedEmployee.Should().NotBeNull();
updatedEmployee!.FirstName.Should().Be("Updated");
updatedEmployee.Salary.Should().Be(80000m);
updatedEmployee.EmployeeId.Should().Be(createdEmployee.EmployeeId);
}
}
}
Create Unit Tests for Services:
Create HRM.Tests/UnitTests/DepartmentServiceTests.cs
:
using Microsoft.EntityFrameworkCore;
using FluentAssertions;
using HRM.API.Data;
using HRM.API.Services;
using HRM.API.Models;
using HRM.API.DTOs;
using Xunit;
namespace HRM.Tests.UnitTests
{
public class DepartmentServiceTests : IDisposable
{
private readonly HrmDbContext _context;
private readonly DepartmentService _service;
public DepartmentServiceTests()
{
var options = new DbContextOptionsBuilder<HrmDbContext>()
.UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
.Options;
_context = new HrmDbContext(options);
_service = new DepartmentService(_context);
// Seed test data
SeedTestData();
}
private void SeedTestData()
{
_context.Departments.AddRange(
new Department { DepartmentId = 1, Name = "HR", Description = "Human Resources", IsActive = true },
new Department { DepartmentId = 2, Name = "IT", Description = "Information Technology", IsActive = true },
new Department { DepartmentId = 3, Name = "Finance", Description = "Finance Department", IsActive = false }
);
_context.SaveChanges();
}
[Fact]
public async Task GetAllDepartmentsAsync_ReturnsAllDepartments()
{
// Act
var result = await _service.GetAllDepartmentsAsync();
// Assert
result.Should().NotBeNull();
result.Should().HaveCount(3);
result.Should().Contain(d => d.Name == "HR");
result.Should().Contain(d => d.Name == "IT");
result.Should().Contain(d => d.Name == "Finance");
}
[Fact]
public async Task GetDepartmentByIdAsync_WithValidId_ReturnsDepartment()
{
// Act
var result = await _service.GetDepartmentByIdAsync(1);
// Assert
result.Should().NotBeNull();
result!.Name.Should().Be("HR");
result.Description.Should().Be("Human Resources");
result.IsActive.Should().BeTrue();
}
[Fact]
public async Task GetDepartmentByIdAsync_WithInvalidId_ReturnsNull()
{
// Act
var result = await _service.GetDepartmentByIdAsync(999);
// Assert
result.Should().BeNull();
}
[Fact]
public async Task CreateDepartmentAsync_WithValidData_CreatesDepartment()
{
// Arrange
var createDto = new CreateDepartmentDto
{
Name = "New Department",
Description = "A new test department",
IsActive = true
};
// Act
var result = await _service.CreateDepartmentAsync(createDto);
// Assert
result.Should().NotBeNull();
result.Name.Should().Be("New Department");
result.Description.Should().Be("A new test department");
result.IsActive.Should().BeTrue();
result.DepartmentId.Should().BeGreaterThan(0);
// Verify it was actually saved
var savedDepartment = await _context.Departments.FindAsync(result.DepartmentId);
savedDepartment.Should().NotBeNull();
savedDepartment!.Name.Should().Be("New Department");
}
[Fact]
public async Task DeleteDepartmentAsync_WithValidId_DeletesDepartment()
{
// Act
var result = await _service.DeleteDepartmentAsync(3); // Delete Finance department
// Assert
result.Should().BeTrue();
// Verify it was actually deleted
var deletedDepartment = await _context.Departments.FindAsync(3);
deletedDepartment.Should().BeNull();
}
[Fact]
public async Task DeleteDepartmentAsync_WithInvalidId_ReturnsFalse()
{
// Act
var result = await _service.DeleteDepartmentAsync(999);
// Assert
result.Should().BeFalse();
}
[Fact]
public async Task DepartmentExistsAsync_WithValidId_ReturnsTrue()
{
// Act
var result = await _service.DepartmentExistsAsync(1);
// Assert
result.Should().BeTrue();
}
[Fact]
public async Task DepartmentExistsAsync_WithInvalidId_ReturnsFalse()
{
// Act
var result = await _service.DepartmentExistsAsync(999);
// Assert
result.Should().BeFalse();
}
public void Dispose()
{
_context.Dispose();
}
}
}
Run All Tests:
# Navigate to test project directory
cd HRM.Tests
# Run all tests
dotnet test
# Run tests with verbose output
dotnet test --verbosity normal
# Run tests and generate coverage report
dotnet test --collect:"XPlat Code Coverage"
# Run specific test class
dotnet test --filter "DepartmentControllerTests"
# Run specific test method
dotnet test --filter "GetDepartments_ReturnsSuccessAndCorrectContentType"
# Run tests by category
dotnet test --filter "Category=Integration"
Run Tests for Different Environments:
# Test against Development environment database
$env:ASPNETCORE_ENVIRONMENT = "Development"
dotnet test --settings test.runsettings
# Test against QA environment database
$env:ASPNETCORE_ENVIRONMENT = "QA"
dotnet test --settings test.runsettings
Create Test Configuration:
Create HRM.Tests/test.runsettings
:
<?xml version="1.0" encoding="utf-8"?>
<RunSettings>
<DataCollectionRunSettings>
<DataCollectors>
<DataCollector friendlyName="XPlat code coverage" uri="datacollector://Microsoft/CodeCoverage/2.0" assemblyQualifiedName="Microsoft.CodeCoverage.Extensions.DataCollectors.DotNetCodeCoverageCollector, Microsoft.CodeCoverage.Extensions, Version=15.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<Configuration>
<Format>json,cobertura,lcov,teamcity,opencover</Format>
</Configuration>
</DataCollector>
</DataCollectors>
</DataCollectionRunSettings>
</RunSettings>
Test Output Analysis:
Expected test results should show:
- ✅ Integration Tests: Verify API endpoints work correctly across environments
- ✅ Unit Tests: Validate business logic in isolation with in-memory database
- ✅ Code Coverage: Measure how much of your code is tested
- ✅ Performance: Ensure tests run quickly (under 30 seconds total)
Benefits of This Testing Approach:
- Isolation: Each test uses fresh in-memory database
- Speed: Fast execution without real database overhead
- Reliability: Consistent results across different environments
- Coverage: Both API layer (integration) and service layer (unit) testing
- CI/CD Ready: Can run in automated build pipelines
Create PowerShell Automation Script:
Create Scripts/Test-HRM-API.ps1
:
param(
[string]$Environment = "Development",
[string]$BaseUrl = "http://localhost:5152"
)
Write-Host "=======================================`n" -ForegroundColor Cyan
Write-Host "HRM API AUTOMATED TEST SUITE`n" -ForegroundColor Yellow
Write-Host "Environment: $Environment`n" -ForegroundColor Green
Write-Host "Base URL: $BaseUrl`n" -ForegroundColor Green
Write-Host "=======================================" -ForegroundColor Cyan
$tests = @()
$startTime = Get-Date
# Test 1: Get all departments
try {
$response = Invoke-RestMethod -Uri "$BaseUrl/api/departments" -Method Get
$tests += @{ Name = "Get Departments"; Status = "PASS"; Count = $response.Count }
Write-Host "✅ Get Departments: PASSED ($($response.Count) departments)" -ForegroundColor Green
} catch {
$tests += @{ Name = "Get Departments"; Status = "FAIL"; Error = $_.Exception.Message }
Write-Host "❌ Get Departments: FAILED - $($_.Exception.Message)" -ForegroundColor Red
}
# Test 2: Get all employees
try {
$response = Invoke-RestMethod -Uri "$BaseUrl/api/employees" -Method Get
$tests += @{ Name = "Get Employees"; Status = "PASS"; Count = $response.Count }
Write-Host "✅ Get Employees: PASSED ($($response.Count) employees)" -ForegroundColor Green
} catch {
$tests += @{ Name = "Get Employees"; Status = "FAIL"; Error = $_.Exception.Message }
Write-Host "❌ Get Employees: FAILED - $($_.Exception.Message)" -ForegroundColor Red
}
# Test 3: Create department
try {
$newDept = @{
name = "Test Department $(Get-Random)"
description = "Automated test department"
isActive = $true
} | ConvertTo-Json
$response = Invoke-RestMethod -Uri "$BaseUrl/api/departments" -Method Post -Body $newDept -ContentType "application/json"
$tests += @{ Name = "Create Department"; Status = "PASS"; Id = $response.departmentId }
Write-Host "✅ Create Department: PASSED (ID: $($response.departmentId))" -ForegroundColor Green
} catch {
$tests += @{ Name = "Create Department"; Status = "FAIL"; Error = $_.Exception.Message }
Write-Host "❌ Create Department: FAILED - $($_.Exception.Message)" -ForegroundColor Red
}
# Calculate summary
$endTime = Get-Date
$duration = ($endTime - $startTime).TotalSeconds
$passedTests = ($tests | Where-Object { $_.Status -eq "PASS" }).Count
$failedTests = ($tests | Where-Object { $_.Status -eq "FAIL" }).Count
$successRate = [math]::Round(($passedTests / $tests.Count) * 100, 2)
# Display summary
Write-Host "`n=======================================" -ForegroundColor Cyan
Write-Host "TEST SUMMARY" -ForegroundColor Yellow
Write-Host "=======================================" -ForegroundColor Cyan
Write-Host "Duration: $duration seconds" -ForegroundColor White
Write-Host "Total Tests: $($tests.Count)" -ForegroundColor White
Write-Host "Passed: $passedTests" -ForegroundColor Green
Write-Host "Failed: $failedTests" -ForegroundColor Red
Write-Host "Success Rate: $successRate%" -ForegroundColor $(if ($successRate -ge 80) { "Green" } else { "Red" })
Write-Host "=======================================" -ForegroundColor Cyan
return $successRate -ge 80
Run Automated Tests:
# Test Development environment
cd HRM.Tests\Scripts
.\Test-HRM-API.ps1 -Environment "Development" -BaseUrl "http://localhost:5152"
# Test QA environment
.\Test-HRM-API.ps1 -Environment "QA" -BaseUrl "http://localhost:5153"
# Test Production environment
.\Test-HRM-API.ps1 -Environment "Production" -BaseUrl "http://localhost:5154"
Step 12: Environment Diagnostics and CORS Configuration
Add Environment Diagnostics (Development only):
// Add to Program.cs for debugging
if (app.Environment.IsDevelopment())
{
app.MapGet("/env", (IConfiguration cfg, IWebHostEnvironment env) => new {
Environment = env.EnvironmentName,
Connection = cfg.GetConnectionString("DefaultConnection"),
ApiSettings = cfg.GetSection("ApiSettings").Get<object>()
});
}
Configure CORS per Environment:
Update appsettings.json
files to include CORS settings:
{
"SecuritySettings": {
"EnableCors": true,
"AllowedOrigins": ["http://localhost:3000", "https://localhost:3001"]
}
}
Step 13: CI/CD Deployment Strategy
Build and Deployment Pipeline:
- Build once, promote through Dev → QA → Prod
- Override connection strings using environment variables in deployment
- Execute database migrations during deployment with proper approvals
- Run automated smoke tests after each deployment
Environment Variable Overrides:
# Override connection string in production
$env:ConnectionStrings__DefaultConnection = "Server=prod-server;Database=HRM_Prod;..."
# Override API settings
$env:ApiSettings__EnableSwagger = "false"
$env:ApiSettings__EnableDetailedErrors = "false"
Post-Deployment Smoke Tests:
# Verify deployment with automated tests
.\Test-HRM-API.ps1 -Environment "Production" -BaseUrl "https://api.company.com"
# Expected: GET /api/departments returns 200 with seeded data
# Expected: All critical endpoints respond within acceptable time limits
Step 10: Troubleshooting
- SQL connection errors: verify port 1433, credentials, and
TrustServerCertificate=true
(or configure certificates). - Missing tables: ensure migrations applied; consider switching from
EnsureCreated()
toDatabase.Migrate()
. - No Swagger: available only in Development via
MapOpenApi()
.
Appendix: Quick command reference (PowerShell)
# Set environment
$env:ASPNETCORE_ENVIRONMENT = "Development" # QA | Production
# Apply migrations
dotnet ef database update --project HRM.API.csproj --startup-project HRM.API.csproj
# Run
dotnet run
That’s it—your HRM API now has a clear, repeatable multi-environment workflow with ASP.NET Core and SQL Server.
Note: How to use VS Code REST Client
Step 1: Install REST Client Extension
- Open VS Code
- Go to Extensions (Ctrl+Shift+X)
- Search for “REST Client” by Huachao Mao
- Click Install
Step 2: Create HTTP Request Files
Create HRM.API/Tests/api-tests.http
in your project:
@baseUrl = http://localhost:5152
@contentType = application/json
### Development Environment Variables
# @name dev
@devUrl = http://localhost:5152
### QA Environment Variables
# @name qa
@qaUrl = http://localhost:5153
### Production Environment Variables
# @name prod
@prodUrl = http://localhost:5154
### ============================================
### DEPARTMENT ENDPOINTS
### ============================================
### Get all departments (Development)
GET /api/departments
Accept:
### Get all departments (QA)
GET /api/departments
Accept:
### Get department by ID
GET /api/departments/1
Accept:
### Create new department
POST /api/departments
Content-Type:
{
"name": "Quality Assurance",
"description": "Software testing and quality control",
"isActive": true
}
### ============================================
### EMPLOYEE ENDPOINTS
### ============================================
### Get all employees
GET /api/employees
Accept:
### Get employee by ID
GET /api/employees/1
Accept:
### Create new employee
POST /api/employees
Content-Type:
{
"firstName": "Jane",
"lastName": "Smith",
"email": "jane.smith@company.com",
"phoneNumber": "+1-555-5678",
"hireDate": "2024-10-12T00:00:00",
"salary": 80000.00,
"departmentId": 1
}
### Update employee
PUT /api/employees/1
Content-Type:
{
"employeeId": 1,
"firstName": "Jane",
"lastName": "Smith-Updated",
"email": "jane.smith@company.com",
"phoneNumber": "+1-555-5678",
"hireDate": "2024-10-12T00:00:00",
"salary": 85000.00,
"departmentId": 1
}
### Delete employee
DELETE /api/employees/1
### ============================================
### ENVIRONMENT-SPECIFIC TESTING
### ============================================
### Test Development Environment Health
GET /env
Accept:
### Test QA Environment
GET /api/departments
Accept:
### Test Production Environment
GET /api/departments
Accept:
Step 3: Create Environment-Specific Request Files
Create HRM.API/Tests/development.http
:
@baseUrl = http://localhost:5152
@environment = Development
### Get all departments
GET /api/departments
### Environment diagnostics (Development only)
GET /env
### Create test department
POST /api/departments
Content-Type: application/json
{
"name": "Test Dept ",
"description": "Auto-generated test department",
"isActive": true
}
Create HRM.API/Tests/qa.http
:
@baseUrl = http://localhost:5153
@environment = QA
### Get all departments
GET /api/departments
### Get all employees
GET /api/employees
### Create QA test data
POST /api/departments
Content-Type: application/json
{
"name": "QA Test Department",
"description": "Department for QA testing purposes",
"isActive": true
}
Step 4: How to Use REST Client
- Start your API in the desired environment:
$env:ASPNETCORE_ENVIRONMENT = "Development" dotnet run --launch-profile development
- Open the .http file in VS Code
- Click “Send Request” above any HTTP request
- You’ll see a clickable “Send Request” link above each
###
section - Or use
Ctrl+Alt+R
keyboard shortcut
- You’ll see a clickable “Send Request” link above each
- View responses in the right panel:
- Response status, headers, and body will appear
- JSON responses are automatically formatted
- Use variables for different environments:
- Change
@baseUrl
variable to switch environments - Use `` for dynamic test data
- Change
Step 5: Advanced REST Client Features
Create HRM.API/Tests/advanced-tests.http
:
@baseUrl = http://localhost:5152
### Create department and store ID for later use
# @name createDept
POST /api/departments
Content-Type: application/json
{
"name": "REST Client Test Dept",
"description": "Created via REST Client",
"isActive": true
}
### Use the created department ID in subsequent requests
@deptId =
### Create employee in the new department
POST /api/employees
Content-Type: application/json
{
"firstName": "REST",
"lastName": "Client",
"email": "rest.client@company.com",
"phoneNumber": "+1-555-",
"hireDate": "",
"salary": 75000.00,
"departmentId":
}
### Clean up - delete the test department
DELETE /api/departments/
Step 6: REST Client Settings
Create .vscode/settings.json
in your HRM project:
{
"rest-client.environmentVariables": {
"development": {
"baseUrl": "http://localhost:5152",
"environment": "Development"
},
"qa": {
"baseUrl": "http://localhost:5153",
"environment": "QA"
},
"production": {
"baseUrl": "http://localhost:5154",
"environment": "Production"
}
},
"rest-client.defaultHeaders": {
"Accept": "application/json",
"Content-Type": "application/json"
}
}
Step 7: Switch Between Environments
- Use Command Palette:
Ctrl+Shift+P
→ “REST Client: Switch Environment” - Select environment: development, qa, or production
- All requests will use the selected environment variables
Benefits of REST Client vs PowerShell:
- ✅ Visual responses: Formatted JSON, status codes, headers
- ✅ Request history: All past requests saved automatically
- ✅ Variables: Chain requests and reuse response data
- ✅ Environment switching: Easy toggle between Dev/QA/Prod
- ✅ Syntax highlighting: Better readability than command line
- ✅ Save/share: Commit .http files to version control for team sharing
Comments