Testing: JUnit 5 + Mockito — Ship Code You Trust
JUnit 5 ও Mockito — বিশ্বাসযোগ্য কোড deliver করার শিল্প
1. Why Test? (test কেন লিখব?)
A bank transfer system without tests is a ticking bomb. Tests are the executable specification of what your code should do — they describe behavior, catch regressions, and let you refactor fearlessly. In Java, the de-facto stack is JUnit 5 for structuring tests and Mockito for replacing external dependencies (databases, payment APIs, time) with controllable stand-ins.
2. Anatomy of a JUnit 5 Test
A JUnit 5 test is just a method in a class, annotated with @Test. JUnit discovers, runs,
and reports each one independently.
@Test annotation দেওয়া method। JUnit আপনার test runner-ই নিজে থেকে এগুলো খুঁজে বার করে চালায়।
import org.junit.jupiter.api.*;
import static org.junit.jupiter.api.Assertions.*;
class CalculatorTest {
Calculator calc;
@BeforeEach
void setUp() { calc = new Calculator(); }
@Test
void addsTwoPositives() {
assertEquals(5, calc.add(2, 3));
}
@Test
void divideByZeroThrows() {
assertThrows(ArithmeticException.class, () -> calc.div(10, 0));
}
}
Expected results (run with Maven): [OK] addsTwoPositives [OK] divideByZeroThrows Tests run: 2, Failures: 0, Errors: 0, Skipped: 0
| Annotation | When it runs | বাংলায় |
|---|---|---|
@Test | Marks a test method. | একটি test method চিহ্নিত করে। |
@BeforeEach | Before every test — fresh fixture. | প্রতিটি test-এর আগে — fresh fixture তৈরি। |
@AfterEach | After every test — cleanup. | প্রতিটি test-এর পর cleanup। |
@BeforeAll | Once before the whole class. | পুরো class-এ একবার শুরুতে। |
@DisplayName | Pretty name for the report. | Report-এ সুন্দর নাম দেখায়। |
3. The Assert Pyramid (পরীক্ষার পিরামিড)
Every test has the same shape: Arrange · Act · Assert. Build the world, run the code, check the outcome.
4. A Runnable Micro-Test Framework (Plain Java)
To feel how JUnit works without the sandbox limitation, here is a stripped-down assertion helper that runs in plain Java. Mentally, JUnit is exactly this — with a prettier runner and more assertion methods.
class Assertions {
static void assertEquals(Object a, Object b, String label) {
if (!a.equals(b)) throw new AssertionError(label + " expected " + a + " got " + b);
System.out.println("[OK] " + label);
}
}
class Calculator {
int add(int a, int b) { return a + b; }
}
class Main {
public static void main(String[] args) {
Calculator c = new Calculator();
Assertions.assertEquals(5, c.add(2, 3), "2+3");
Assertions.assertEquals(0, c.add(-2, 2), "-2+2");
System.out.println("All tests passed.");
}
}
5. Mockito — Faking Dependencies
Unit tests must be fast and isolated. A test that hits a real bKash API over the network every run is not a unit test — it is a liability. Mockito gives you disposable stand-ins for any interface or class.
import org.junit.jupiter.api.Test;
import static org.mockito.Mockito.*;
import static org.junit.jupiter.api.Assertions.*;
interface PaymentGateway {
boolean charge(String user, int taka);
}
class OrderService {
private final PaymentGateway gw;
OrderService(PaymentGateway gw) { this.gw = gw; }
boolean place(String user, int taka) { return gw.charge(user, taka); }
}
class OrderServiceTest {
@Test
void callsGatewayAndReturnsResult() {
PaymentGateway fakeGw = mock(PaymentGateway.class);
when(fakeGw.charge("arif", 500)).thenReturn(true);
OrderService svc = new OrderService(fakeGw);
assertTrue(svc.place("arif", 500));
verify(fakeGw).charge("arif", 500);
}
}
Expected (run locally with Maven): [OK] callsGatewayAndReturnsResult Tests: 1 passed
6. Unit vs Integration Tests
✅ Unit Tests
- Test ONE class, mock its collaborators
- Milliseconds per test
- Run on every save, in every CI build
- 80% of your suite
🔍 Integration Tests
- Test MANY classes together, real DB or container
- Seconds per test
- Spring Boot
@SpringBootTest, Testcontainers - 20% of your suite
Testing pyramid — নিচে বহু দ্রুত unit test, মাঝে কম কিছু integration test, এবং উপরে অল্প কিছু end-to-end test। Pyramid স্থিতিশীল; উল্টো pyramid (বেশিরভাগ slow E2E test) দুঃস্বপ্ন।
7. Vocabulary
| Term | Meaning | বাংলায় |
|---|---|---|
| Fixture | The objects a test needs to run. | Test চালানোর জন্য প্রয়োজনীয় object। |
| Assertion | A check that fails the test if false. | একটি যাচাই; false হলে test fail। |
| Mock | A stand-in object with scripted behavior. | নকল object — আপনি script করে দেন। |
| Stub | A mock that only returns canned values. | শুধু নির্দিষ্ট মান return করে। |
| Spy | Real object, but you can also verify calls. | সত্যিকার object, কিন্তু call যাচাই করা যায়। |
| Flaky test | A test that passes and fails without code changes. | কারণ ছাড়াই কখনো pass কখনো fail — unreliable। |
8. Practice Problems
Try each one, then reveal the answer.
-
Write three plain-Java assertions for an
add(int,int)method: 2+3=5, 0+0=0, and -1+1=0. Run them.সাধারণ Java দিয়ে একটিadd(int,int)method-এর জন্য তিনটি assertion লিখুন — 2+3=5, 0+0=0, -1+1=0।✨ Show Answer
Main.javaclass Main { static int add(int a, int b) { return a + b; } static void check(int exp, int got, String t) { if (exp != got) throw new AssertionError(t + " failed"); System.out.println("[OK] " + t); } public static void main(String[] args) { check(5, add(2, 3), "2+3"); check(0, add(0, 0), "0+0"); check(0, add(-1, 1), "-1+1"); } } -
Why is
@BeforeEachpreferred over a constructor-initialised field for the "fixture"?@BeforeEach-কে constructor দিয়ে field initialise করার চেয়ে কেন বেশি পছন্দ করা হয়?✨ Show Answer
Answer: JUnit 5 creates a new instance of the test class for every test method, so a field assignment in the constructor also works. BUT
@BeforeEachis clearer to the reader, survives any future change to JUnit's lifecycle, and pairs naturally with@AfterEachcleanup — it makes the setup/teardown intent explicit. -
Build a tiny hand-rolled mock: an
Emailerinterface withsend(to, body), a fake that records every call into aList, and verify the count.একটি ছোট Mockito-মতো mock বানান —Emailerinterface, একটি ভুয়া implementation যা সব call record করবে, তারপর count verify করুন।✨ Show Answer
Main.javaimport java.util.*; interface Emailer { void send(String to, String body); } class FakeEmailer implements Emailer { List<String> calls = new ArrayList<>(); public void send(String to, String body) { calls.add(to + ":" + body); } } class Notifier { private final Emailer e; Notifier(Emailer e) { this.e = e; } void welcome(String user) { e.send(user, "Welcome!"); } } class Main { public static void main(String[] args) { FakeEmailer fake = new FakeEmailer(); Notifier n = new Notifier(fake); n.welcome("fatima@abcltech.com"); if (fake.calls.size() != 1) throw new AssertionError("expected 1 email"); System.out.println("[OK] recorded " + fake.calls); } } -
Name three signs of a "flaky" test and one strategy to eliminate each.একটি test flaky কিনা বোঝার তিনটি লক্ষণ বলুন ও প্রতিটির সমাধান দিন।
✨ Show Answer
Answer: (1) Depends on wall-clock time — use a
Clockthat can be injected. (2) Depends on a shared database — use per-test transactions that roll back, or Testcontainers for isolation. (3) Depends on iteration order of aHashMap— useLinkedHashMapor assert on a sorted view.(১) wall-clock-এর উপর নির্ভর — inject-করা
Clockব্যবহার করুন। (২) shared database-এর উপর নির্ভর — per-test transaction বা Testcontainers। (৩)HashMap-এর iteration order —LinkedHashMapবা sorted view-এর উপর assert। -
Demonstrate parameterized testing manually: loop over three test-case arrays and run the same assertion for each.হাতে-হাতে parameterized test বানান: তিনটি test-case array-র উপর loop চালিয়ে একই assertion করুন।
✨ Show Answer
Main.javaclass Main { static boolean isEven(int n) { return n % 2 == 0; } public static void main(String[] args) { int[] inputs = { 0, 1, 2, 3, 100 }; boolean[] expect = { true, false, true, false, true }; for (int i = 0; i < inputs.length; i++) { boolean got = isEven(inputs[i]); if (got != expect[i]) throw new AssertionError("case " + i); System.out.println("[OK] isEven(" + inputs[i] + ") = " + got); } } }
Summary — Module 43
JUnit 5 structures tests with @Test, @BeforeEach, and rich
assertions. Mockito fakes collaborators so tests stay fast and isolated. Think
Arrange · Act · Assert, favour many fast unit tests over a few slow end-to-end ones, and never ship
code whose behaviour isn't pinned down by a test.