হ্যালো হাসিব! তোমার দেওয়া Integration Testing-এর লেকচার Transcript-টি আমি খুব ভালোভাবে পড়েছি। চলো, লেকচারের সম্পূর্ণ বিষয়বস্তু স্টেপ-বাই-স্টেপ ব্রেকডাউন করি, যাতে কোনো কিছুই বাদ না যায়।

📝 Lecture Summary (Quick Revision)

  • Unit Test বনাম Integration Test: Unit Test শুধুমাত্র একটি নির্দিষ্ট কম্পোনেন্ট (যেমন: Controller) টেস্ট করে। আর Integration Test ব্রাউজারের বদলে HttpClient ব্যবহার করে সম্পূর্ণ Application Stack (Controller -> Service -> Repository -> Database) টেস্ট করে।
  • Custom WebApplicationFactory তৈরি: Integration Test-এর জন্য অ্যাপটিকে Test Environment-এ রান করাতে WebApplicationFactory ইনহেরিট করে একটি কাস্টম ফ্যাক্টরি ক্লাস তৈরি করা হয়।
  • Program Class অ্যাক্সেস করা: .NET 6+ এর Top-level statements-এর কারণে Program ক্লাস বাই-ডিফল্ট টেস্ট প্রজেক্টে পাওয়া যায় না। তাই Program.cs-এর শেষে public partial class Program { } লিখতে হয়।
  • csproj ফাইলে পারমিশন: ASP.NET Core প্রজেক্টের ইন্টারনাল ক্লাসগুলো টেস্ট প্রজেক্টে পাওয়ার জন্য csproj ফাইলে InternalsVisibleTo ট্যাগ অ্যাড করতে হয়।
  • In-Memory Database ব্যবহার: প্রোডাকশনের আসল SQL Server ডাটাবেস টেস্টে ব্যবহার করা অনুচিত। তাই ConfigureWebHost ওভাররাইড করে রিয়েল DbContext রিমুভ করে EntityFrameworkCore.InMemory ডাটাবেস অ্যাড করা হয়।
  • Environment চেকিং: Test Environment-এ অপ্রয়োজনীয় কোড (যেমন: PDF জেনারেশন) যাতে এক্সিকিউট না হয়, সেজন্য IsEnvironment("Test") কন্ডিশন ব্যবহার করা হয়।
  • Test Execution: HttpClient দিয়ে HTTP GET রিকোয়েস্ট পাঠানো হয় এবং রেসপন্স স্ট্যাটাস কোড (২০০ OK) BeSuccessful() দিয়ে ভ্যালিডেট করা হয়।

🚀 Comprehensive Breakdown

১. Unit Testing vs Integration Testing [Priority: 8/10]

  • Unit Testing: এখানে তুমি শুধু একটি সিঙ্গেল কম্পোনেন্ট নিয়ে কাজ করবে। যেমন, Controller টেস্ট করার সময় Service বা Repository ঠিকমতো কাজ করছে কিনা, তা নিয়ে চিন্তা করার দরকার নেই। ফোকাস শুধু Controller-এর রিটার্ন ভ্যালুর ওপর থাকে।
  • Integration Testing: এখানে overall functionality চেক করা হয়। ব্রাউজার থেকে রিকোয়েস্ট না পাঠিয়ে, প্রোগ্রামাটিক্যালি HttpClient অবজেক্টের মাধ্যমে রিকোয়েস্ট পাঠানো হয়। এই রিকোয়েস্টটি application-এর সমস্ত লেয়ার (Controller -> Service -> Repository -> DbContext) পার হয়ে কাজ করে। শেষে Controller থেকে আসা রেসপন্সটি ভ্যালিড কিনা, তা চেক করা হয়।
  • Why: রিয়েল ওয়ার্ল্ডে ব্রাউজার ছাড়াই তোমার API বা MVC Route-গুলো সম্পূর্ণ আর্কিটেকচার ফলো করে ঠিকঠাক রেসপন্স দিচ্ছে কিনা, তা নিশ্চিত করতেই Integration Test করা হয়।

২. Integration Test Class সেটআপ করা [Priority: 7/10]

Lecturer এখানে PersonsController-এর Index রাউটের জন্য একটি টেস্ট তৈরি করেছেন।

  • VS Shortcut: Visual Studio-তে Right click -> Add -> New Item -> Class.
  • VS Code Shortcut: VS Code-এ তুমি Ctrl + N চেপে নতুন ফাইল বানাতে পারো অথবা টার্মিনালে dotnet new class -n PersonsControllerIntegrationTest কমান্ড লিখতে পারো।
using Xunit;
using FluentAssertions;
using System.Net.Http;
using System.Threading.Tasks;
 
public class PersonsControllerIntegrationTest
{
    // HTTP Client-এর রেফারেন্স রাখার জন্য
    private readonly HttpClient _client;
 
    // Constructor-এর মাধ্যমে Factory রিসিভ করা হবে (পরবর্তীতে দেখানো হয়েছে)
    
    [Fact]
    public async Task Index_ShouldReturnSuccess()
    {
        // Arrange (Client setup will be here)
        
        // Act: GetAsync দিয়ে রিকোয়েস্ট পাঠানো
        HttpResponseMessage response = await _client.GetAsync("/persons/index");
 
        // Assert: রেসপন্স 200 OK কিনা চেক করা
        response.Should().BeSuccessful();
    }
}
 

৩. Program Class এবং csproj কনফিগারেশন [Priority: 9/10]

টেস্ট প্রজেক্ট থেকে মূল প্রজেক্টকে রান করাতে গেলে মূল প্রজেক্টের Program ক্লাসটিকে অ্যাক্সেস করতে হয়। কিন্তু Top-Level Statements-এর কারণে এটি হাইড থাকে।

  • Why: কম্পাইলার নিজে থেকে একটি Program ক্লাস বানায়, যা টেস্ট প্রজেক্ট দেখতে পায় না। তাই একে partial হিসেবে ডিক্লেয়ার করতে হয়।

Program.cs (মূল প্রজেক্ট): ফাইলের একদম শেষে এটি লিখতে হবে।

// All existing configurations...
app.Run();
 
public partial class Program { } // টেস্ট প্রজেক্টের জন্য এক্সপোজ করা হলো
 

csproj ফাইল মডিফিকেশন: মূল প্রজেক্টের .csproj ফাইলে নিচের কোডটুকু যোগ করতে হবে যাতে টেস্ট প্রজেক্ট পারমিশন পায়।

<ItemGroup>
  <InternalsVisibleTo Include="CRUDTests" />
</ItemGroup>
 

৪. Custom WebApplicationFactory তৈরি [Priority: 10/10]

এটি এই লেকচারের সবচেয়ে গুরুত্বপূর্ণ অংশ। WebApplicationFactory ইনহেরিট করে আমরা আমাদের অ্যাপটিকে টেস্টের জন্য মেমোরিতে হোস্ট করি।

  • NuGet Packages Requirement:

  • Visual Studio-তে NuGet Package Manager থেকে ইনস্টল করা যায়।

  • VS Code-এ টার্মিনালে লিখবে: dotnet add package Microsoft.AspNetCore.Mvc.Testing dotnet add package Microsoft.EntityFrameworkCore.InMemory

  • In-Memory Database কেন? আসল SQL Server ডাটাবেসে টেস্ট ডেটা ইনসার্ট করা ব্যাড প্র্যাকটিস। এতে মেইন ডাটাবেস নষ্ট হতে পারে এবং টেস্ট স্লো হয়। InMemory ডাটাবেস প্রতিবার রান করার সময় একদম ফ্রেশ এবং এম্পটি ডাটাবেস তৈরি করে।

CustomWebApplicationFactory.cs Implementation:

using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using System.Linq;
 
public class CustomWebApplicationFactory : WebApplicationFactory<Program>
{
    protected override void ConfigureWebHost(IWebHostBuilder builder)
    {
        builder.UseEnvironment("Test"); // Environment Test সেট করা হলো
 
        builder.ConfigureServices(services =>
        {
            // ১. বিদ্যমান আসল SQL Server DbContext খুঁজে বের করা
            var descriptor = services.SingleOrDefault(temp => 
                temp.ServiceType == typeof(DbContextOptions<ApplicationDbContext>));
 
            // ২. যদি পাওয়া যায়, তবে সেটি রিমুভ করে দেওয়া
            if (descriptor != null)
            {
                services.Remove(descriptor);
            }
 
            // ৩. নতুন করে In-Memory Database অ্যাড করা
            services.AddDbContext<ApplicationDbContext>(options =>
            {
                options.UseInMemoryDatabase("DatabaseForTesting"); 
            });
        });
    }
}
 

৫. Factory-কে Test Class-এ ইনজেক্ট করা [Priority: 8/10]

এখন IClassFixture ব্যবহার করে আমরা টেস্ট ক্লাসে আমাদের বানানো কাস্টম ফ্যাক্টরিটি ইনজেক্ট করব এবং HttpClient তৈরি করব।

public class PersonsControllerIntegrationTest : IClassFixture<CustomWebApplicationFactory>
{
    private readonly HttpClient _client;
 
    public PersonsControllerIntegrationTest(CustomWebApplicationFactory factory)
    {
        // ফ্যাক্টরি থেকে ক্লায়েন্ট তৈরি করা হলো
        _client = factory.CreateClient();
    }
 
    [Fact]
    public async Task Index_ShouldReturnSuccess()
    {
        // Act
        HttpResponseMessage response = await _client.GetAsync("/persons/index");
 
        // Assert
        response.Should().BeSuccessful();
    }
}
 

৬. Environment Specific Code Troubleshooting [Priority: 7/10]

টেস্ট রান করার পর একটি এরর এসেছিল: folder containing wkhtmltopdf.exe is not found

  • Why: Program.cs-এ PDF জেনারেট করার কনফিগারেশন ছিল। Integration Test-এ যেহেতু সম্পূর্ণ Program.cs এক্সিকিউট হয়, তাই সে ঐ ফোল্ডার খুঁজতে গিয়ে ক্র্যাশ করে।
  • Solution: Program.cs-এ কন্ডিশন বসিয়ে দেওয়া, যাতে এটি “Test” Environment-এ এক্সিকিউট না হয়।

Program.cs Update:

// Test এনভায়রনমেন্ট না হলেই কেবল PDF কনফিগারেশন রান করবে
if (!builder.Environment.IsEnvironment("Test"))
{
    // wkhtmltopdf configuration code...
}
 

🏆 Best Practices & .NET 10 Updates

Best Practices for Integration Testing (MVC/Web API):

  1. Isolated Database: এই লেকচারে যেমনটা করা হয়েছে, সবসময় প্রোডাকশন ডাটাবেস থেকে টেস্ট ডাটাবেসকে আলাদা রাখবে। In-Memory DB ভালো, তবে আরও রিয়েলিস্টিক টেস্টিংয়ের জন্য বর্তমানে Testcontainers (Docker ভিত্তিক) অনেক বেশি জনপ্রিয়।
  2. Clean up: প্রতিটি টেস্ট শেষে ডাটাবেসের ডেটা ক্লিন করে নেওয়া ভালো, যাতে একটি টেস্ট অন্য টেস্টের রেজাল্টে প্রভাব না ফেলে (যদিও In-Memory প্রতিবার নতুন ক্রিয়েট হয়, তবুও খেয়াল রাখা উচিত)।
  3. Fluent Assertions: Assert.Equal() এর বদলে response.Should().BeSuccessful() ব্যবহার করা অনেক বেশি রিডেবল।

.NET 10 Perspectives: .NET 10-এও Integration Testing-এর কোর কনসেপ্ট একই আছে। WebApplicationFactory এবং IClassFixture এখনো স্ট্যান্ডার্ড। তবে:

  • .NET 10-এ Minimal Hosting আরও বেশি অপ্টিমাইজড। তুমি চাইলে public partial class Program { } লেখার পরিবর্তে তোমার csproj ফাইলে সরাসরি <InternalsVisibleTo Include="YourTestProjectName" /> রাখলেই অনেক সময় কাজ হয়ে যায়, তবে partial class মেথডটি এখনো সবচেয়ে নির্ভরযোগ্য এবং বহুল ব্যবহৃত।
  • Web API-এর ক্ষেত্রে: রেসপন্স বডি JSON হিসেবে আসবে। তুমি .NET 10-এর System.Net.Http.Json লাইব্রেরি ব্যবহার করে খুব সহজেই রেসপন্স বডি ডিসিরিয়ালাইজ করতে পারবে:
var result = await response.Content.ReadFromJsonAsync<List<PersonResponse>>();
 

(লেকচারের শেষের দিকে বলেছে রেসপন্স বডি কীভাবে টেস্ট করতে হয় তা পরের লেকচারে দেখাবে। তাই এটা তোমার জন্য একটা বোনাস অ্যাডভান্সড টিপস!)