package lab.mockito; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.*; import org.junit.jupiter.api.Test; import org.mockito.MockitoSession; import org.mockito.exceptions.misusing.PotentialStubbingProblem; import org.mockito.exceptions.misusing.UnnecessaryStubbingException; import org.mockito.quality.Strictness; /** Uses the MockitoSession API so the strictness failures can be caught and recorded. */ class StrictnessTest { private static void finish(MockitoSession session) { try { session.finishMocking(); } catch (UnnecessaryStubbingException ignored) { // the demo stubs are deliberately left unused in some tests } } private MockitoSession strict() { return org.mockito.Mockito.mockitoSession().strictness(Strictness.STRICT_STUBS).startMocking(); } @Test void unusedStubIsReportedWhenTheSessionEnds() { Transcript t = new Transcript("10-unnecessary-stubbing.txt"); MockitoSession session = strict(); PaymentGateway gateway = mock(PaymentGateway.class); when(gateway.isAvailable()).thenReturn(true); // used below when(gateway.charge("acc-1", 100)).thenReturn(null); // never used gateway.isAvailable(); UnnecessaryStubbingException e = assertThrows(UnnecessaryStubbingException.class, session::finishMocking); t.block("UnnecessaryStubbingException:", e.getMessage()); t.write(); } @Test void sameMethodDifferentArgumentsIsAPotentialStubbingProblem() { Transcript t = new Transcript("11-potential-stubbing-problem.txt"); MockitoSession session = strict(); try { PaymentGateway gateway = mock(PaymentGateway.class); when(gateway.isAvailable()).thenReturn(true); when(gateway.charge("acc-1", 900)).thenReturn(new Receipt("r", "acc-1", 900, true)); CheckoutService svc = new CheckoutService(gateway, mock(AuditLog.class), new PriceCalculator()); // 1000 cents with 0% discount reaches charge("acc-1", 1000) inside CheckoutService.java, // a different source file from this test: Mockito flags the argument mismatch. PotentialStubbingProblem e = assertThrows(PotentialStubbingProblem.class, () -> svc.checkout("acc-1", 1000, 0)); t.block("PotentialStubbingProblem:", e.getMessage()); } finally { finish(session); } t.write(); } @Test void sameFileMismatchIsNotFlagged() { Transcript t = new Transcript("11b-same-file-mismatch-not-flagged.txt"); MockitoSession session = strict(); try { PaymentGateway gateway = mock(PaymentGateway.class); when(gateway.charge("acc-1", 900)).thenReturn(new Receipt("r", "acc-1", 900, true)); // Same call-shape as the test above, but made from this very file. Receipt r = assertDoesNotThrow(() -> gateway.charge("acc-1", 1000)); t.line("charge(\"acc-1\", 1000) from the test file returns : %s (no PotentialStubbingProblem)", r); assertNull(r); } finally { finish(session); } t.write(); } @Test void lenientStubsAreExemptAndStrictnessCanBeLoosenedPerStub() { Transcript t = new Transcript("12-lenient.txt"); MockitoSession session = strict(); PaymentGateway gateway = mock(PaymentGateway.class); lenient().when(gateway.charge("acc-1", 100)).thenReturn(new Receipt("r", "acc-1", 100, true)); lenient().when(gateway.isAvailable()).thenReturn(true); Receipt other = gateway.charge("acc-1", 999); // arg mismatch, but the stub is lenient t.line("lenient stub, mismatching call returns : %s", other); assertNull(other); session.finishMocking(); // unused lenient stub: no exception t.line("finishMocking with unused lenient stubs : no exception"); t.write(); } @Test void plainMockWithoutSessionIsLenient() { Transcript t = new Transcript("13-no-session-no-checks.txt"); PaymentGateway gateway = mock(PaymentGateway.class); when(gateway.charge("acc-1", 100)).thenReturn(new Receipt("r", "acc-1", 100, true)); Receipt other = gateway.charge("acc-1", 999); t.line("mismatching call on a plain mock returns : %s (no strictness error)", other); assertNull(other); t.write(); } }