Von Dr. Cassian Holt, Senior Architect & Programming Language Historian bei Java Fleet Systems Consulting in Essen-Rüttenscheid


🔗 Bisher in der Testing-Time-Travel-Serie:

Heute: Teil 4 – Mocking & Test Doubles Deep-Dive


📝 Kurze Zusammenfassung

🎭 Test Doubles & Mocking:

  • Was sind Test Doubles und warum brauchen wir sie?
  • Mockito von Grund auf – das Standard-Mocking-Framework
  • Rückblick: Wo wir schon unbewusst gemockt haben
  • Spring Boot @MockBean vs. klassisches Mocking

🔍 Bonus-Thema: Contract Testing:

  • Consumer-Driven Contracts mit Pact
  • API-Verträge zwischen Services testen

🎯 Diese Woche: Von isolierten Unit Tests zu vertrauenswürdigen Service-Verträgen!


🎭 Der große Mocking-Rückblick: „Ups, haben wir schon die ganze Zeit gemockt!“

Dr. Cassian hier – und heute wird’s interessant! Nova kam gestern zu mir: „Cassian, in deinen Beispielen verwendest du dauernd @MockBean und @Mock – aber was ist das eigentlich genau?“

Gute Frage! 🤔 Beim Durchblick durch unsere Serie fiel mir auf: Wir haben schon in Teil 2 und 3 gemockt, ohne es richtig zu erklären!

Zeit für eine ehrliche Aufarbeitung – heute holen wir alles nach! 🧹

🔍 Wo wir schon heimlich gemockt haben:

Teil 2 (Test-Pyramide):

// Das hier war schon Mocking!
@SpringBootTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.ANY)
class TaskServiceIntegrationTest {
    
    @MockBean  // ← MOCKING! Haben wir nicht erklärt
    private EmailService emailService;
    
    @Test
    void shouldCreateTaskWithoutSendingEmail() {
        // Implizites Mocking - EmailService tut nichts
    }
}

Teil 3 (Property-Based Testing):

// Auch das war Mocking!
@ExtendWith(MockitoExtension.class)  // ← MOCKING-Framework!
class PropertyBasedSecurityTest {
    
    @Mock  // ← Klassisches Mock-Objekt
    private UserRepository userRepository;
    
    // Haben wir verwendet, aber nicht erklärt!
}

Nova’s Reaktion: „Oh wow, ich dachte das wären einfach nur Annotations!“ 😅


🎭 Test Doubles: Die Schauspieler deiner Tests

📚 Die Theorie: Gerard Meszaros‘ Test Double Patterns

Wie im Film: Manchmal braucht man Stunt-Doubles statt echter Schauspieler!

// Das Problem ohne Test Doubles:
@Test
void shouldProcessPayment() {
    PaymentService service = new PaymentService();
    
    // 😱 Das würde echtes Geld überweisen!
    // 😱 Braucht echte Datenbank!
    // 😱 Braucht Internet-Verbindung!
    // 😱 Dauert 5 Sekunden!
    PaymentResult result = service.processPayment(request);
}

🎯 Die 5 Arten von Test Doubles:

// 1. DUMMY - Objekt das nur da ist, aber nie verwendet wird
class DummyEmailService implements EmailService {
    public void sendEmail(String to, String subject, String body) {
        // Tut nichts - ist nur Parameter-Filler
    }
}

// 2. STUB - Gibt vordefinierte Antworten zurück
class StubPaymentGateway implements PaymentGateway {
    public PaymentResult process(PaymentRequest request) {
        // Immer erfolgreich - vordefinierte Antwort
        return PaymentResult.success("STUB_12345");
    }
}

// 3. SPY - Echtes Objekt, aber zeichnet Aufrufe auf
class SpyAuditLogger extends AuditLogger {
    private List<String> recordedLogs = new ArrayList<>();
    
    @Override
    public void log(String message) {
        recordedLogs.add(message);  // Aufzeichnung
        super.log(message);         // Dann echter Aufruf
    }
    
    public List<String> getRecordedLogs() { return recordedLogs; }
}

// 4. MOCK - Vordefinierte Erwartungen + Verifikation
// (Wird von Framework wie Mockito erstellt)

// 5. FAKE - Vereinfachte, funktionierende Implementierung
class FakeUserRepository implements UserRepository {
    private Map<Long, User> users = new HashMap<>();
    
    public User save(User user) {
        user.setId(System.currentTimeMillis()); // Einfache ID-Generation
        users.put(user.getId(), user);
        return user;
    }
    
    public Optional<User> findById(Long id) {
        return Optional.ofNullable(users.get(id));
    }
}

🔧 Mockito Deep-Dive: Das Schweizer Taschenmesser des Mockings

🚀 Mockito Setup und Basics

// Maven Dependency
<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-core</artifactId>
    <version>5.8.0</version>
    <scope>test</scope>
</dependency>

// Mockito in JUnit 5 integrieren
@ExtendWith(MockitoExtension.class)
class PaymentServiceTest {
    
    // 3 Wege, Mocks zu erstellen:
    
    // 1. Mit Annotation (empfohlen)
    @Mock
    private PaymentGateway paymentGateway;
    
    // 2. Programmatisch im Test
    @Test
    void manualMockCreation() {
        PaymentGateway gateway = Mockito.mock(PaymentGateway.class);
    }
    
    // 3. Mit @InjectMocks für die getestete Klasse
    @InjectMocks
    private PaymentService paymentService; // Bekommt automatisch alle @Mock-Objekte injiziert
}

🎯 Mock-Verhalten konfigurieren: when().thenReturn()

@Test
void shouldHandleSuccessfulPayment() {
    // ARRANGE: Mock-Verhalten definieren
    PaymentRequest request = createValidPaymentRequest();
    PaymentResult expectedResult = PaymentResult.success("TXN_12345");
    
    when(paymentGateway.processPayment(request))
        .thenReturn(expectedResult);
    
    // ACT: Getestete Methode aufrufen
    PaymentResult actualResult = paymentService.processPayment(request);
    
    // ASSERT: Ergebnis prüfen
    assertThat(actualResult)
        .isEqualTo(expectedResult)
        .extracting(PaymentResult::getTransactionId)
        .isEqualTo("TXN_12345");
}

@Test
void shouldHandlePaymentFailure() {
    // Mock kann auch Exceptions werfen
    when(paymentGateway.processPayment(any(PaymentRequest.class)))
        .thenThrow(new PaymentGatewayException("Network timeout"));
    
    assertThatThrownBy(() -> paymentService.processPayment(createValidPaymentRequest()))
        .isInstanceOf(PaymentProcessingException.class)
        .hasMessageContaining("Gateway error");
}

🔍 Mock-Verifikation: verify() – „Wurde aufgerufen?“

@Test
void shouldLogAuditEventAfterPayment() {
    // Arrange
    PaymentRequest request = createValidPaymentRequest();
    when(paymentGateway.processPayment(request))
        .thenReturn(PaymentResult.success("TXN_999"));
    
    // Act
    paymentService.processPayment(request);
    
    // Assert - Verifikation der Interaktionen
    verify(auditLogger).log("Payment processed: TXN_999");
    verify(auditLogger, times(1)).log(anyString());
    
    // Erweiterte Verifikationen
    verify(emailService, never()).sendEmail(anyString(), anyString(), anyString());
    verify(paymentGateway, atLeastOnce()).processPayment(request);
}

@Test
void shouldCallServicesInCorrectOrder() {
    // Act
    paymentService.processPayment(createValidPaymentRequest());
    
    // Verify order of calls
    InOrder inOrder = inOrder(validator, paymentGateway, auditLogger);
    inOrder.verify(validator).validate(any(PaymentRequest.class));
    inOrder.verify(paymentGateway).processPayment(any(PaymentRequest.class));
    inOrder.verify(auditLogger).log(anyString());
}

🎨 Advanced Mocking Patterns

class AdvancedMockingTest {
    
    @Test
    void shouldHandleComplexMockingScenarios() {
        // Conditional Mocking - abhängig vom Input
        when(paymentGateway.processPayment(argThat(request -> 
            request.getAmount().compareTo(new BigDecimal("1000")) > 0)))
            .thenReturn(PaymentResult.success("HIGH_VALUE_TXN"));
        
        when(paymentGateway.processPayment(argThat(request -> 
            request.getAmount().compareTo(new BigDecimal("1000")) <= 0)))
            .thenReturn(PaymentResult.success("NORMAL_TXN"));
        
        // Callback-basiertes Mocking
        when(paymentGateway.processPayment(any()))
            .thenAnswer(invocation -> {
                PaymentRequest request = invocation.getArgument(0);
                return PaymentResult.success("CALLBACK_" + request.getUserId());
            });
    }
    
    @Test
    void shouldCaptureMockArguments() {
        // Argument Captors - fange übergebene Parameter ab
        ArgumentCaptor<PaymentRequest> requestCaptor = 
            ArgumentCaptor.forClass(PaymentRequest.class);
        
        paymentService.processPayment(createValidPaymentRequest());
        
        verify(paymentGateway).processPayment(requestCaptor.capture());
        
        PaymentRequest capturedRequest = requestCaptor.getValue();
        assertThat(capturedRequest.getAmount())
            .isEqualByComparingTo(new BigDecimal("299.99"));
        assertThat(capturedRequest.getCurrency()).isEqualTo("EUR");
    }
    
    @Test
    @DisplayName("🎭 Should handle partial mocking with spy")
    void shouldUseSpyForPartialMocking() {
        // Spy - teilweise echtes Objekt, teilweise gemockt
        List<String> realList = new ArrayList<>();
        List<String> spyList = spy(realList);
        
        // Normale Methoden funktionieren wie gewohnt
        spyList.add("item1");
        spyList.add("item2");
        assertThat(spyList).hasSize(2);
        
        // Aber wir können spezielle Methoden mocken
        when(spyList.size()).thenReturn(100);
        assertThat(spyList.size()).isEqualTo(100); // Gemockt
        assertThat(spyList.get(0)).isEqualTo("item1"); // Echter Aufruf
    }
}

🌱 Spring Boot Mocking: @MockBean vs. @Mock

🤝 Integration Testing mit @MockBean

@SpringBootTest
class TaskServiceIntegrationTest {
    
    @Autowired
    private TaskService taskService; // Echter Spring Bean
    
    @MockBean // Spring Boot Mock - ersetzt Bean im ApplicationContext
    private EmailService emailService;
    
    @MockBean
    private AuditService auditService;
    
    @Test
    void shouldCreateTaskWithSpringContext() {
        // Spring Boot startet (fast) vollständig, aber mit gemockten Beans
        Task task = new Task("Integration Test Task");
        
        when(emailService.isValidEmail(anyString())).thenReturn(true);
        
        Task savedTask = taskService.createTask(task);
        
        assertThat(savedTask.getId()).isNotNull();
        verify(auditService).logTaskCreated(savedTask.getId());
        // EmailService wurde durch Mock ersetzt - kein echter Email-Versand!
    }
}

📊 @Mock vs. @MockBean Entscheidungsmatrix

Kriterium@Mock (Mockito)@MockBean (Spring Boot)
Speed⚡ Sehr schnell🐌 Langsamer (Spring Context)
ScopeUnit TestIntegration Test
DependenciesNur MockitoSpring Boot + Mockito
Use CaseIsolierte Klassen-TestsService-Layer mit Spring
SetupManuell Dependencies injizierenAutomatisch durch Spring
// Wann @Mock verwenden:
@ExtendWith(MockitoExtension.class)
class PaymentCalculatorTest {
    @Mock private TaxService taxService;        // Reine Unit-Tests
    @Mock private DiscountService discountService;
    @InjectMocks private PaymentCalculator calculator;
    
    // Schnell, isoliert, keine Spring-Dependencies
}

// Wann @MockBean verwenden:
@SpringBootTest
class OrderProcessingIntegrationTest {
    @Autowired private OrderService orderService;  // Echter Spring Bean
    @MockBean private PaymentGateway paymentGateway; // Mock im Spring Context
    
    // Langsamer, aber testet echte Spring-Integration
}

🏆 Mocking Best Practices: Was Nova gelernt hat

✅ Do’s – Die goldenen Mocking-Regeln

class GoodMockingExamples {
    
    @Test
    @DisplayName("✅ Mock only direct dependencies")
    void shouldMockOnlyDirectDependencies() {
        // Gut: Nur direkte Dependencies der getesteten Klasse mocken
        @Mock PaymentGateway gateway;  // PaymentService → PaymentGateway
        @Mock AuditLogger logger;      // PaymentService → AuditLogger
        
        // Schlecht wäre: Interne Details des PaymentGateway zu mocken
        // @Mock HttpClient httpClient; // Das ist PaymentGateway's Problem!
    }
    
    @Test
    @DisplayName("✅ Use meaningful mock data")
    void shouldUseMeaningfulTestData() {
        // Gut: Realistische Test-Daten
        when(paymentGateway.processPayment(any()))
            .thenReturn(PaymentResult.builder()
                .transactionId("TXN_20241201_001")
                .status(PaymentStatus.SUCCESS)
                .processedAt(LocalDateTime.now())
                .build());
        
        // Schlecht: Bedeutungslose Dummy-Daten
        // .thenReturn(PaymentResult.builder().transactionId("test").build());
    }
    
    @Test
    @DisplayName("✅ Verify important interactions")
    void shouldVerifyImportantInteractions() {
        paymentService.processPayment(createHighValuePayment());
        
        // Gut: Geschäftskritische Interaktionen verifizieren
        verify(auditLogger).logHighValueTransaction(anyString());
        verify(fraudDetectionService).checkTransaction(any());
        
        // Schlecht: Jede einzelne Interaktion verifizieren
        // verify(logger, times(1)).log(anyString()); // Over-verification!
    }
}

❌ Don’ts – Typische Mocking-Fallen

class BadMockingExamples {
    
    @Test
    @DisplayName("❌ Don't mock value objects")
    void dontMockValueObjects() {
        // Schlecht: Value Objects sollten NICHT gemockt werden
        // @Mock LocalDateTime mockTime;
        // @Mock BigDecimal mockAmount;
        // @Mock String mockString;
        
        // Gut: Echte Value Objects verwenden
        LocalDateTime realTime = LocalDateTime.now();
        BigDecimal realAmount = new BigDecimal("99.99");
        String realString = "Real String";
    }
    
    @Test
    @DisplayName("❌ Don't mock the class under test")
    void dontMockClassUnderTest() {
        // Schlecht: Die getestete Klasse selbst mocken
        // PaymentService mockPaymentService = mock(PaymentService.class);
        // when(mockPaymentService.processPayment(any())).thenReturn(result);
        // Das testet nichts!
        
        // Gut: Echte Instanz mit gemockten Dependencies
        PaymentService realService = new PaymentService(mockGateway, mockLogger);
    }
    
    @Test
    @DisplayName("❌ Don't over-specify mocks")
    void dontOverSpecifyMocks() {
        // Schlecht: Zu spezifische Mock-Konfiguration
        when(gateway.processPayment(
            argThat(req -> req.getAmount().equals(new BigDecimal("99.99")) &&
                          req.getCurrency().equals("EUR") &&
                          req.getUserId() == 12345L)))
            .thenReturn(successResult());
        
        // Gut: Flexible Mock-Konfiguration
        when(gateway.processPayment(any(PaymentRequest.class)))
            .thenReturn(successResult());
    }
}

🎯 Der „Tell, Don’t Ask“ Trick für Mocks

class TellDontAskMockingTest {
    
    @Test
    @DisplayName("🎯 Focus on behavior, not state")
    void shouldFocusOnBehaviorNotState() {
        // Tell-Don't-Ask: Was PASSIERT ist wichtig, nicht WAS zurückgegeben wird
        
        PaymentRequest vipCustomerRequest = createVipCustomerRequest();
        
        paymentService.processPayment(vipCustomerRequest);
        
        // Fokus: Verhalten verifizieren (was wurde aufgerufen?)
        verify(vipNotificationService).notifyVipPayment(vipCustomerRequest);
        verify(loyaltyService).addPoints(eq(vipCustomerRequest.getUserId()), gt(100));
        
        // Nicht so wichtig: Exakte Return-Values
        // (solange das Verhalten stimmt)
    }
}

🤝 Bonus-Thema: Contract Testing – Mocks die halten, was sie versprechen

Das Problem mit traditionellen Mocks:

// Dieser Mock lügt! 😱
@Test
void traditionalMockLiesAboutAPI() {
    when(paymentService.processPayment(any()))
        .thenReturn(PaymentResult.success("fake-id"));
    
    // Was ist, wenn die echte PaymentService API sich ändert?
    // Was ist, wenn sie jetzt PaymentException wirft?
    // Was ist, wenn das Response-Format anders ist?
    
    // Mock weiß nichts davon - Test bleibt grün! 💚
    // Production bricht! 💥
}

🤝 Consumer-Driven Contracts mit Pact

// Maven Dependencies
<dependency>
    <groupId>au.com.dius.pact.consumer</groupId>
    <artifactId>junit5</artifactId>
    <version>4.4.5</version>
    <scope>test</scope>
</dependency>

// Consumer Test - Frontend definiert Contract
@ExtendWith(PactConsumerTestExt.class)
@PactTestFor(providerName = "payment-service")
class PaymentServiceContractTest {
    
    @Pact(consumer = "task-frontend")
    public RequestResponsePact createPaymentContract(PactDslWithProvider builder) {
        return builder
            .given("user has valid payment method")
            .uponReceiving("payment request")
            .path("/api/payments")
            .method("POST")
            .headers(Map.of("Content-Type", "application/json"))
            .body(newJsonBody(payment -> {
                payment.numberType("amount", 99.99);
                payment.stringType("currency", "EUR");
                payment.numberType("userId", 12345);
            }).build())
            .willRespondWith()
            .status(201)
            .headers(Map.of("Content-Type", "application/json"))
            .body(newJsonBody(response -> {
                response.stringType("transactionId", "TXN_12345");
                response.stringValue("status", "SUCCESS");
                response.datetime("processedAt", "yyyy-MM-dd'T'HH:mm:ss");
            }).build())
            .toPact();
    }
    
    @Test
    @PactTestFor(pactMethod = "createPaymentContract")
    void shouldCreatePaymentAccordingToContract(MockServer mockServer) {
        // Frontend Code gegen Contract-Mock testen
        PaymentClient client = new PaymentClient(mockServer.getUrl());
        
        PaymentRequest request = PaymentRequest.builder()
            .amount(new BigDecimal("99.99"))
            .currency("EUR")
            .userId(12345L)
            .build();
        
        PaymentResult result = client.processPayment(request);
        
        // Contract enforcement - diese Struktur MUSS stimmen!
        assertThat(result.getTransactionId()).isNotNull();
        assertThat(result.getStatus()).isEqualTo("SUCCESS");
        assertThat(result.getProcessedAt()).isNotNull();
    }
}

🛡️ Provider Verification – Backend hält Contract ein

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@Provider("payment-service")
@PactFolder("pacts")
class PaymentServiceProviderTest {
    
    @LocalServerPort
    private int port;
    
    @BeforeEach
    void setUp(PactVerificationContext context) {
        context.setTarget(new HttpTestTarget("localhost", port));
    }
    
    @State("user has valid payment method")
    void userHasValidPaymentMethod() {
        // Test-State einrichten
        createUserWithValidPaymentMethod(12345L);
    }
    
    @TestTemplate
    @ExtendWith(PactVerificationInvocationContextProvider.class)
    void pactVerificationTestTemplate(PactVerificationContext context) {
        // Verifiziert: Backend erfüllt Frontend-Contract!
        context.verifyInteraction();
    }
}

Der Contract-Testing Vorteil:

// Contract Testing verhindert diese Situation:
// Frontend erwartet:     { "transactionId": "string", "status": "SUCCESS" }
// Backend liefert:       { "id": 12345, "result": "OK" }
//                        ↑ Contract-Test würde fehlschlagen! ⚠️

// Traditionelle Mocks würden das nicht erkennen!

💡 Action Items für Mocking & Contract Testing

🎯 Level 1: Mocking Basics (diese Woche)

  1. @Mock und @MockBean in einem bestehenden Test verwenden
  2. when().thenReturn() für Happy Path implementieren
  3. verify() für wichtige Interaktionen einbauen
  4. ArgumentCaptor für komplexe Parameter-Prüfung ausprobieren

🎯 Level 2: Advanced Mocking (nächste Woche)

  1. @Spy für Partial Mocking verwenden
  2. Custom ArgumentMatcher schreiben
  3. InOrder für Aufruf-Reihenfolge prüfen
  4. @InjectMocks korrekt konfigurieren

🎯 Level 3: Contract Testing (nächster Monat)

  1. Pact zu Maven/Gradle hinzufügen
  2. Consumer-Contract für wichtigste API schreiben
  3. Provider-Verification implementieren
  4. Breaking Changes durch Contract-Tests verhindern

🎯 Level 4: Production-Ready Mocking (Quartalsziel)

Challenge: „Zero-Surprise API Changes“

  • Alle kritischen Service-Interfaces mit Contract Tests absichern
  • Mock-Strategy für jedes Team-Projekt definieren
  • CI/CD Pipeline mit Contract-Verification einrichten

🎉 Fazit: Mocking ist eine Kunst, kein Zufall!

Was wir heute gelernt haben:

🎭 Test Doubles = Die 5 Schauspieler deiner Tests

  • Dummy, Stub, Spy, Mock, Fake – jeder hat seinen Platz
  • Mockito macht Mock-Erstellung trivial
  • @Mock vs. @MockBean – wähle das richtige Tool

🔧 Mockito Mastery

  • when().thenReturn() für Verhalten definieren
  • verify() für Interaktions-Prüfung
  • ArgumentCaptor für komplexe Assertions

🤝 Contract Testing = Mocks die nicht lügen

  • Consumer-Driven Contracts mit Pact
  • API-Verträge automatisch verifizieren
  • Breaking Changes verhindern BEVOR sie Production erreichen

Für Nova und alle Lernenden:

Mocking ist kein „Schummeln“! Es ist professionelle Test-Isolation. Aber: Mock nur was du musst, nicht was du kannst. Und: Contract-Tests verhindern die „funktioniert bei mir“-Momente!

Für alle Team-Leads:

Invest in Contract Testing! Die 2-3 Stunden Setup sparen euch Wochen von „Warum ist Production kaputt?“-Debugging.

Wie Nova nach dem Mocking-Deep-Dive sagte: „Jetzt verstehe ich endlich, warum meine Tests so schnell sind – sie reden gar nicht mit der echten Datenbank!“ 🚀


📝 Kurze Zusammenfassung

🎭 Test Doubles & Mocking:

  • 5 Arten: Dummy, Stub, Spy, Mock, Fake – jede für verschiedene Szenarien
  • Mockito als Standard-Framework für Java-Mocking
  • @Mock für Unit Tests, @MockBean für Spring Integration Tests

🤝 Contract Testing:

  • Consumer-Driven Contracts mit Pact für Java
  • API-Verträge zwischen Services automatisch verifizieren
  • Breaking Changes verhindern bevor sie Production erreichen

🎯 Nächste Woche: Security-Testing & Legacy Code – Code Sentinel zeigt Security-Automation!


FAQ – Mocking & Contract Testing

Frage 1: Wann verwende ich @Mock und wann @MockBean?
Antwort: @Mock für reine Unit Tests (schnell, isoliert), @MockBean für Spring Integration Tests (langsamer, aber mit echtem Spring Context). Faustregel: Brauchst du Spring-Features? → @MockBean. Ansonsten → @Mock.

Frage 2: Soll ich jeden Service-Call mocken?
Antwort: Nein! Mock nur direkte Dependencies der getesteten Klasse. Interne Details der Dependencies sind deren Problem. Über-Mocking macht Tests brittle und schwer wartbar.

Frage 3: Wie viele verify()-Aufrufe sind okay?
Antwort: Verifiziere nur geschäftskritische Interaktionen. Nicht jede Zeile Code! Rule of Thumb: Max 2-3 verify() pro Test. Mehr deutet auf zu komplexe Tests hin.

Frage 4: Contract Testing vs Integration Testing – was ist der Unterschied?
Antwort: Contract Testing prüft API-Kompatibilität zwischen Services. Integration Tests prüfen vollständige Workflows. Contract Tests sind schneller und finden Breaking Changes früher.

Frage 5: Kann ich Contract Testing für Legacy-APIs verwenden?
Antwort: Ja! Starte mit Charakterisierungs-Contracts – dokumentiere das aktuelle API-Verhalten. Dann schrittweise echte Contracts einführen. Golden Master + Pact = perfekte Legacy-Strategy.


Dr. Cassian Holt verbindet historische Testing-Evolution mit modernen Mocking-Strategien. Nova’s Mocking-Journey zeigt: Von unbewusster Verwendung zu bewusster Meisterschaft!


Tags: #Mocking #TestDoubles #Mockito #SpringBoot #ContractTesting #Pact #UnitTesting #IntegrationTesting


Avatar-Foto

Cassian Holt

35 Jahre alt, promovierter Informatiker mit Spezialisierung auf Programming Language Theory. Cassian arbeitet als Senior Architect bei Java Fleet Systems Consulting und bringt eine einzigartige wissenschaftliche Perspektive in praktische Entwicklungsprojekte. Seine Leidenschaft: Die Evolution von Programmiersprachen und warum "neue" Features oft alte Konzepte sind.

0 Kommentare

Schreibe einen Kommentar

Avatar-Platzhalter

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert