Mockito mock objects library core API and implementation for comprehensive Java unit testing
—
This section covers Behavior-Driven Development (BDD) style testing using Mockito's BDDMockito class, which provides given/when/then syntax for more readable tests.
BDD-style testing focuses on behavior specification using natural language patterns.
// Traditional Mockito
when(userService.findUser("john")).thenReturn(johnUser);
verify(userService).findUser("john");
// BDD-style with BDDMockito
given(userService.findUser("john")).willReturn(johnUser);
then(userService).should().findUser("john");import static org.mockito.BDDMockito.*;
// This includes all BDD methods plus standard Mockito methodsOrganize tests using the given/when/then pattern.
@Test
void shouldCreateUserWhenValidDataProvided() {
// GIVEN - Set up test conditions
User inputUser = new User("john", "john@example.com");
User savedUser = new User("john", "john@example.com", 1L);
given(userRepository.save(any(User.class))).willReturn(savedUser);
given(emailService.isValidEmail(anyString())).willReturn(true);
// WHEN - Execute the behavior being tested
User result = userService.createUser(inputUser);
// THEN - Verify the outcomes
then(userRepository).should().save(inputUser);
then(emailService).should().isValidEmail("john@example.com");
assertThat(result.getId()).isEqualTo(1L);
}Configure mock behavior using given() instead of when().
public static <T> BDDMyOngoingStubbing<T> given(T methodCall)
interface BDDMyOngoingStubbing<T> {
BDDMyOngoingStubbing<T> willReturn(T value);
BDDMyOngoingStubbing<T> willReturn(T value, T... values);
BDDMyOngoingStubbing<T> willThrow(Throwable... throwables);
BDDMyOngoingStubbing<T> willThrow(Class<? extends Throwable> throwableType);
BDDMyOngoingStubbing<T> willAnswer(Answer<?> answer);
BDDMyOngoingStubbing<T> willCallRealMethod();
BDDMyOngoingStubbing<T> willDoNothing();
T getMock();
}Usage Examples:
@Test
void shouldHandleUserCreationScenarios() {
// GIVEN successful validation
given(validationService.validateUser(any(User.class)))
.willReturn(ValidationResult.SUCCESS);
// GIVEN repository will save and return user with ID
given(userRepository.save(any(User.class)))
.willReturn(userWithId);
// GIVEN email service will send welcome email
given(emailService.sendWelcomeEmail(any(User.class)))
.willReturn(true);
// WHEN creating user
CreateUserResult result = userService.createUser(newUserData);
// THEN all collaborators should be called appropriately
then(validationService).should().validateUser(any(User.class));
then(userRepository).should().save(any(User.class));
then(emailService).should().sendWelcomeEmail(any(User.class));
}BDD alternatives to doXxx() methods.
public static BDDStubber willReturn(Object toBeReturned)
public static BDDStubber willReturn(Object toBeReturned, Object... toBeReturnedNext)
public static BDDStubber willThrow(Throwable... toBeThrown)
public static BDDStubber willThrow(Class<? extends Throwable> throwableType)
public static BDDStubber willAnswer(Answer<?> answer)
public static BDDStubber willDoNothing()
public static BDDStubber willCallRealMethod()
interface BDDStubber {
<T> T given(T mock);
}Usage Examples:
@Test
void shouldHandleEmailServiceFailures() {
// GIVEN email service will fail on first call, succeed on second
willThrow(EmailException.class)
.willReturn(true)
.given(emailService).sendEmail(anyString());
// GIVEN notification service will do nothing when called
willDoNothing().given(notificationService).notifyAdmin(anyString());
// WHEN processing with retry logic
boolean result = emailProcessor.sendWithRetry("message");
// THEN service should retry and eventually succeed
then(emailService).should(times(2)).sendEmail("message");
then(notificationService).should().notifyAdmin("Email failed on first attempt");
assertTrue(result);
}Verify interactions using then().should() syntax.
public static <T> Then<T> then(T mock)
interface Then<T> {
BDDInOrder<T> should();
BDDInOrder<T> should(VerificationMode mode);
BDDInOrder<T> shouldHaveNoInteractions();
BDDInOrder<T> shouldHaveNoMoreInteractions();
BDDInOrder<T> shouldHaveZeroInteractions(); // Deprecated
}Usage Examples:
@Test
void shouldVerifyUserServiceInteractions() {
// GIVEN
given(userRepository.existsByEmail(anyString())).willReturn(false);
given(userRepository.save(any(User.class))).willReturn(savedUser);
// WHEN
userService.registerUser("john@example.com", "password");
// THEN - verify specific interactions
then(userRepository).should().existsByEmail("john@example.com");
then(userRepository).should().save(any(User.class));
then(emailService).should().sendWelcomeEmail(savedUser);
// THEN - verify no unexpected interactions
then(userRepository).shouldHaveNoMoreInteractions();
then(emailService).shouldHaveNoMoreInteractions();
}
@Test
void shouldVerifyInteractionCounts() {
// WHEN
for (int i = 0; i < 3; i++) {
cacheService.get("key" + i);
}
// THEN
then(cacheService).should(times(3)).get(anyString());
then(cacheService).should(never()).put(anyString(), any());
then(cacheService).should(atLeast(1)).get(startsWith("key"));
}Verify interaction order using BDD syntax.
interface BDDInOrder<T> {
T should();
T should(VerificationMode mode);
void shouldHaveNoMoreInteractions();
}Usage Examples:
@Test
void shouldProcessPaymentInCorrectOrder() {
// GIVEN
given(paymentValidator.validate(any())).willReturn(true);
given(paymentGateway.charge(any())).willReturn(successResult);
given(orderService.updateStatus(any(), any())).willReturn(updatedOrder);
// WHEN
paymentProcessor.processPayment(payment);
// THEN - verify order of operations
InOrder inOrder = inOrder(paymentValidator, paymentGateway, orderService);
then(paymentValidator).should(inOrder).validate(payment);
then(paymentGateway).should(inOrder).charge(payment);
then(orderService).should(inOrder).updateStatus(payment.getOrderId(), PAID);
}Structure complex scenarios with clear BDD language.
class OrderProcessingBDDTest {
@Test
void shouldCompleteOrderWhenPaymentSucceeds() {
// GIVEN an order with valid items
Order order = orderWithValidItems();
given(inventoryService.checkAvailability(any())).willReturn(true);
// AND payment processing will succeed
given(paymentService.processPayment(any())).willReturn(paymentSuccess());
// AND shipping service is available
given(shippingService.scheduleDelivery(any())).willReturn(deliveryScheduled());
// WHEN processing the order
OrderResult result = orderProcessor.processOrder(order);
// THEN inventory should be checked
then(inventoryService).should().checkAvailability(order.getItems());
// AND payment should be processed
then(paymentService).should().processPayment(order.getPayment());
// AND delivery should be scheduled
then(shippingService).should().scheduleDelivery(order);
// AND order should be completed successfully
assertThat(result.getStatus()).isEqualTo(OrderStatus.COMPLETED);
assertThat(result.getTrackingNumber()).isNotNull();
}
@Test
void shouldFailOrderWhenPaymentFails() {
// GIVEN an order with valid items
Order order = orderWithValidItems();
given(inventoryService.checkAvailability(any())).willReturn(true);
// BUT payment processing will fail
given(paymentService.processPayment(any()))
.willThrow(new PaymentFailedException("Insufficient funds"));
// WHEN processing the order
OrderResult result = orderProcessor.processOrder(order);
// THEN inventory should be checked
then(inventoryService).should().checkAvailability(order.getItems());
// AND payment should be attempted
then(paymentService).should().processPayment(order.getPayment());
// BUT shipping should not be attempted
then(shippingService).should(never()).scheduleDelivery(any());
// AND order should fail with appropriate status
assertThat(result.getStatus()).isEqualTo(OrderStatus.PAYMENT_FAILED);
assertThat(result.getError()).contains("Insufficient funds");
}
}Create domain-specific matchers for more readable assertions.
class CustomBDDMatchers {
public static ArgumentMatcher<User> validUser() {
return user -> user != null &&
user.getEmail() != null &&
user.getName() != null &&
user.getName().length() > 0;
}
public static ArgumentMatcher<Order> orderWithAmount(BigDecimal expectedAmount) {
return order -> order != null &&
order.getTotalAmount().equals(expectedAmount);
}
@Test
void shouldUseCustomMatchers() {
// GIVEN
given(userRepository.save(argThat(validUser()))).willReturn(savedUser);
// WHEN
userService.createUser("john", "john@example.com");
// THEN
then(userRepository).should().save(argThat(validUser()));
then(orderService).should().createOrder(argThat(orderWithAmount(new BigDecimal("99.99"))));
}
}Use BDD syntax with spies for partial mocking.
@Test
void shouldCallRealMethodsSelectivelyWithSpy() {
// GIVEN a spy that calls real methods by default
UserService userServiceSpy = spy(new UserService(userRepository));
// BUT will return mock data for findUser
given(userServiceSpy.findUser(anyString())).willReturn(mockUser);
// WHEN calling updateUser (which internally calls findUser)
userServiceSpy.updateUser("john", updateData);
// THEN both real and mocked methods should be called appropriately
then(userServiceSpy).should().findUser("john"); // Mocked
then(userServiceSpy).should().updateUser("john", updateData); // Real method
then(userRepository).should().save(any(User.class)); // Real method called save
}Install with Tessl CLI
npx tessl i tessl/maven-org-mockito--mockito-core