Testing: JUnit 5 + Mockito — Ship Code You Trust

JUnit 5 ও Mockito — বিশ্বাসযোগ্য কোড deliver করার শিল্প

Read: ~30 min Intermediate 5 practice problems Live assert demo

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.

Test ছাড়া কোড production-এ পাঠানো মানে user-দের QA দল হিসেবে ব্যবহার করা। Test হলো আপনার কোডের executable specification — এটি behavior বর্ণনা করে, regression ধরে, এবং refactor করার সাহস দেয়। Java-তে সবচেয়ে বেশি ব্যবহৃত stack — JUnit 5 (test framework) ও Mockito (mock library)।
Sandbox note: JUnit 5 and Mockito are third-party libraries. Install locally with Maven to run these examples — the browser sandbox only runs plain Java without third-party dependencies. The plain-assert demo in §4 below DOES run here.

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.

JUnit 5 test মানে শুধুই একটি class-এর ভেতরে @Test annotation দেওয়া method। JUnit আপনার test runner-ই নিজে থেকে এগুলো খুঁজে বার করে চালায়।
CalculatorTest.java
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
AnnotationWhen it runsবাংলায়
@TestMarks a test method.একটি test method চিহ্নিত করে।
@BeforeEachBefore every test — fresh fixture.প্রতিটি test-এর আগে — fresh fixture তৈরি।
@AfterEachAfter every test — cleanup.প্রতিটি test-এর পর cleanup।
@BeforeAllOnce before the whole class.পুরো class-এ একবার শুরুতে।
@DisplayNamePretty 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.

প্রতিটি test-এর একই গঠন — Arrange · Act · Assert। আগে পরিবেশ তৈরি করুন, তারপর code চালান, শেষে outcome যাচাই করুন।
The Test Pyramid · Arrange → Act → Assert Arrange Build the fixture new Calculator() mock(PaymentAPI.class) Act Invoke the behaviour int r = calc.add(2, 3); Assert Verify the outcome assertEquals(5, r) verify(api).charge(...) Figure 43.1 — Arrange / Act / Assert — প্রতিটি test এই তিনটি ধাপেই লেখা উচিত।

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.

JUnit আসলে অল্প কয়েকটি সাহায্যকারী method নিয়ে গড়ে ওঠা। নিচের ছোট assertion helper চালিয়ে দেখুন — সেটিই JUnit-এর মূল ধারণা।
Main.java
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.

Unit test দ্রুত এবং isolated হতে হবে। একটি test যদি প্রতিবার bKash-এর live API-তে হিট করে, সেটা unit test নয় — dangerous। Mockito-র কাজ হলো যেকোনো interface বা class-এর ভুয়া (mock) সংস্করণ তৈরি করে দেওয়া।
OrderServiceTest.java
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
The classic testing pyramid: many fast unit tests, fewer integration tests, and a few end-to-end tests at the top. A pyramid is stable; an inverted pyramid (mostly slow E2E tests) is a nightmare.

Testing pyramid — নিচে বহু দ্রুত unit test, মাঝে কম কিছু integration test, এবং উপরে অল্প কিছু end-to-end test। Pyramid স্থিতিশীল; উল্টো pyramid (বেশিরভাগ slow E2E test) দুঃস্বপ্ন।

7. Vocabulary

TermMeaningবাংলায়
FixtureThe objects a test needs to run.Test চালানোর জন্য প্রয়োজনীয় object।
AssertionA check that fails the test if false.একটি যাচাই; false হলে test fail।
MockA stand-in object with scripted behavior.নকল object — আপনি script করে দেন।
StubA mock that only returns canned values.শুধু নির্দিষ্ট মান return করে।
SpyReal object, but you can also verify calls.সত্যিকার object, কিন্তু call যাচাই করা যায়।
Flaky testA test that passes and fails without code changes.কারণ ছাড়াই কখনো pass কখনো fail — unreliable।

8. Practice Problems

Try each one, then reveal the answer.

আগে নিজে চেষ্টা করুন, তারপর উত্তর দেখুন।
  1. 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.java
    class 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");
        }
    }
  2. Why is @BeforeEach preferred 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 @BeforeEach is clearer to the reader, survives any future change to JUnit's lifecycle, and pairs naturally with @AfterEach cleanup — it makes the setup/teardown intent explicit.

  3. Build a tiny hand-rolled mock: an Emailer interface with send(to, body), a fake that records every call into a List, and verify the count.
    একটি ছোট Mockito-মতো mock বানান — Emailer interface, একটি ভুয়া implementation যা সব call record করবে, তারপর count verify করুন।
    ✨ Show Answer
    Main.java
    import 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);
        }
    }
  4. 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 Clock that 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 a HashMap — use LinkedHashMap or assert on a sorted view.

    (১) wall-clock-এর উপর নির্ভর — inject-করা Clock ব্যবহার করুন। (২) shared database-এর উপর নির্ভর — per-test transaction বা Testcontainers। (৩) HashMap-এর iteration order — LinkedHashMap বা sorted view-এর উপর assert।

  5. 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.java
    class 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.

JUnit 5 test structure দেয়, Mockito external dependency ভুয়া করে দেয়। Arrange–Act–Assert মনে রাখুন; অনেক দ্রুত unit test লেখেন, কম integration test। Test ছাড়া কোনো কোড production-এ পাঠাবেন না।

Next Module → Build Systems — Maven এবং Gradle দিয়ে dependency ও build পরিচালনা।