হ্যালো হাসিব! এটি টেস্টিং সেকশনের সবচেয়ে অ্যাডভান্সড এবং ইন্টারেস্টিং একটি লেকচার। আমরা আগের লেকচারে রিপোজিটরি মকিংয়ের কনসেপ্ট শিখেছিলাম, আর এখানে পুরো PersonsServiceTest ক্লাসটিকে নতুন আর্কিটেকচার (Moq + Repository Pattern) অনুযায়ী রিফ্যাক্টর করা হয়েছে।
এই লেকচারে লেকচারার বেশ কিছু “Anti-pattern” (যেমন: একটি টেস্টে একাধিক মেথড কল করা) রিমুভ করে ক্লিন টেস্টিং প্র্যাকটিস দেখিয়েছেন।
চলো লেকচারটির একটি কুইক সামারি এবং বিস্তারিত পোস্টমর্টেম করে ফেলি।
📝 Lecture Summary at a Glance
- Single Responsibility Rule: একটি ইউনিট টেস্টে শুধুমাত্র একটি মেথডই টেস্ট করতে হবে।
GetAllPersonsটেস্ট করার জন্য আগে ম্যানুয়ালি ৩ বারAddPersonকল করা হতো, যা এখন পুরোপুরি রিমুভ করে সরাসরিList<Person>তৈরি করা হয়েছে। - Fixing Circular Reference:
PersonএবংCountryএকে অপরকে রেফারেন্স করায় AutoFixture ইনফিনিট লুপে পড়ে এরর দিচ্ছিল। তাইPersonবানানোর সময়Countryপ্রপার্টিকেnullকরে এই এরর ফিক্স করা হয়েছে। - Mocking Specific Methods: যে সার্ভিস মেথডটি টেস্ট করা হচ্ছে, সেটি ইন্টারনালি রিপোজিটরির যে মেথডটিকে কল করে, ঠিক শুধু সেই মেথডটিকেই
Setup().ReturnsAsync()দিয়ে মক করা হয়েছে। - Handling Expressions in Moq:
GetFilteredPersonsটেস্ট করার সময় প্যারামিটারে ল্যাম্বডা এক্সপ্রেশন থাকায়It.IsAny<Expression<Func<Person, bool>>>()ব্যবহার করে মক করা হয়েছে।
🧠 Comprehensive Breakdown & Deep Dive
১. Fixing the “AutoFixture Circular Reference” Error [Importance: 10/10]
- The Problem: লেকচারার যখন টেস্ট রান করলেন, তখন একটি বিশাল এরর আসলো: “AutoFixture was unable to create an instance… traversed object graph contains a circular reference.”
- The Reason:
Personক্লাসের ভেতরেCountryঅবজেক্ট আছে, আবারCountryক্লাসের ভেতরেICollection<Person>আছে। AutoFixture যখন ডামিPersonবানায়, সে নিজে থেকেই একটিCountryবানাতে যায়, আবার ওইCountryবানাতে গিয়ে সে নতুনPersonবানাতে যায়—এভাবে ইনফিনিট লুপ তৈরি হয়! - The Fix: AutoFixture-কে বলে দেওয়া হয়েছে যে,
Personবানানোর সময়Countryপ্রপার্টিকে ইগনোর (null) করতে হবে।
💻 Code Implementation:
var person = _fixture.Build<Person>()
.With(temp => temp.Email, "someone@example.com")
// সার্কুলার রেফারেন্স ব্রেক করা হলো
.With(temp => temp.Country, null as Country)
.Create();
(Pro Tip: আগের একটি লেকচারে আমি OmitOnRecursionBehavior অ্যাড করার কথা বলেছিলাম, সেটি কনস্ট্রাক্টরে অ্যাড করা থাকলে এই .With(..., null) বারবার লেখা লাগে না!)
২. Mocking with It.IsAny [Importance: 9/10]
- The “Why”: যখন তুমি জানো না বা তোমার মাথাব্যথা নেই যে মেথডের প্যারামিটারে এক্সাক্টলি কী ভ্যালু আসবে, কিন্তু তুমি চাও যে মেথডটি কল হলেই যেন একটি নির্দিষ্ট ভ্যালু রিটার্ন হয়, তখন
It.IsAny<T>()ব্যবহার করা হয়।
💻 Code Implementation (GetPersonByPersonId Test):
// "যদি রিপোজিটরির GetPersonByPersonId কল হয় এবং প্যারামিটারে যেকোনো একটি Guid আসে,
// তবে তুমি আমার বানানো 'person' অবজেক্টটি রিটার্ন করবে।"
_personRepositoryMock
.Setup(temp => temp.GetPersonByPersonId(It.IsAny<Guid>()))
.ReturnsAsync(person);
৩. Mocking Methods with Lambda Expressions [Importance: 10/10]
- The Challenge:
GetFilteredPersonsমেথডটি প্যারামিটারে একটি ল্যাম্বডা এক্সপ্রেশন (Predicate) রিসিভ করে। এটিকে মক করা একটু ট্রিকি। - The Solution:
It.IsAnyএর ভেতরে এক্সপ্রেশনের পুরো ডেফিনিশনটা (Expression<Func<T, bool>>) বলে দিতে হয়।
💻 Code Implementation:
_personRepositoryMock
// ল্যাম্বডা এক্সপ্রেশন মক করা হচ্ছে
.Setup(temp => temp.GetFilteredPersons(It.IsAny<Expression<Func<Person, bool>>>()))
.ReturnsAsync(personList); // ডামি লিস্ট রিটার্ন করা হচ্ছে
🚀 Modern Clean Architecture Notes & Pro Tips
The “Test Method Naming Convention” (Industry Standard):
হাসিব, লেকচারার টেস্ট মেথডের নাম লেখার জন্য একটি চমৎকার রুলস ফলো করেছেন:
[MethodName]_[Scenario]_[ExpectedResult]
উদাহরণ: AddPerson_FullPersonDetails_ToBeSuccessful
এটি বর্তমান ইন্ডাস্ট্রির (এবং মাইক্রোসফটের) স্ট্যান্ডার্ড প্র্যাকটিস। এতে করে টেস্ট এক্সপ্লোরার দেখলেই বোঝা যায় কোন মেথডে কী টেস্ট করা হয়েছে এবং কেন ফেইল করেছে। তোমার “Chatrabash” প্রজেক্টে এই নেমিং কনভেনশনটি অবশ্যই ফলো করবে।
পরবর্তী লেকচারে এই ক্লাসের বাকি মেথডগুলোর (যেমন Sort, Update, Delete) মকিং কমপ্লিট করা হবে। তুমি রেডি হলে পরের ট্রান্সক্রিপ্টটি দিতে পারো!