A comprehensive Java mocking framework that enables developers to create test doubles for unit testing.
—
BDDMockito provides Behavior Driven Development syntax for Mockito, using given/when/then structure that aligns with BDD testing practices. This makes tests more readable and follows natural language patterns.
Replace when().thenReturn() with given().willReturn():
public static <T> BDDMyOngoingStubbing<T> given(T methodCall);
public 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();
T getMock();
}Traditional Mockito vs BDD Style:
// Traditional Mockito style
@Test
public void shouldReturnUserWhenValidId() {
// Setup
when(userRepository.findById(123)).thenReturn(user);
// Execute
User result = userService.getUser(123);
// Verify
assertEquals(user, result);
}
// BDD style
@Test
public void shouldReturnUserWhenValidId() {
// Given
given(userRepository.findById(123)).willReturn(user);
// When
User result = userService.getUser(123);
// Then
assertEquals(user, result);
}public static <T> BDDStubber willReturn(T value);
public static BDDStubber willThrow(Throwable... throwables);
public static BDDStubber willThrow(Class<? extends Throwable> throwableType);
public static BDDStubber willAnswer(Answer<?> answer);
public static BDDStubber willDoNothing();
public static BDDStubber willCallRealMethod();Usage Examples:
// Return values
given(calculator.add(2, 3)).willReturn(5);
given(userService.findByEmail("user@example.com")).willReturn(user);
// Multiple return values
given(randomService.nextInt()).willReturn(1, 2, 3);
// Throw exceptions
given(paymentService.processPayment(invalidCard)).willThrow(PaymentException.class);
given(databaseService.connect()).willThrow(new SQLException("Connection failed"));
// Custom answers
given(timeService.getCurrentTime()).willAnswer(invocation -> Instant.now());
// Call real method (for spies)
given(spy.calculate(anyInt())).willCallRealMethod();Use the will-do pattern for stubbing void methods:
// Traditional style
doThrow(new RuntimeException()).when(emailService).sendEmail(anyString());
doNothing().when(auditService).log(anyString());
// BDD style
willThrow(new RuntimeException()).given(emailService).sendEmail(anyString());
willDoNothing().given(auditService).log(anyString());Complete Example:
@Test
public void shouldHandleEmailFailure() {
// Given
willThrow(EmailException.class).given(emailService).sendEmail(anyString());
// When
assertThrows(EmailException.class, () -> {
userService.registerUser("john@example.com", "John");
});
// Then
verify(auditService).logError(contains("Email sending failed"));
}Use then() for BDD-style verification:
public static <T> Then<T> then(T mock);
public interface Then<T> {
T should();
T should(VerificationMode mode);
T shouldHaveNoMoreInteractions();
T shouldHaveZeroInteractions();
}Usage Examples:
// Traditional verification
verify(emailService).sendEmail("user@example.com");
verify(auditService, times(2)).log(anyString());
verifyNoMoreInteractions(emailService);
// BDD verification
then(emailService).should().sendEmail("user@example.com");
then(auditService).should(times(2)).log(anyString());
then(emailService).shouldHaveNoMoreInteractions();@RunWith(MockitoJUnitRunner.class)
public class UserServiceBDDTest {
@Mock private UserRepository userRepository;
@Mock private EmailService emailService;
@Mock private AuditService auditService;
@InjectMocks private UserService userService;
@Test
public void shouldCreateUserAndSendWelcomeEmail() {
// Given
User newUser = new User("john@example.com", "John Doe");
given(userRepository.save(any(User.class))).willReturn(newUser);
given(emailService.isValidEmail("john@example.com")).willReturn(true);
// When
User result = userService.createUser("john@example.com", "John Doe");
// Then
then(result).isEqualTo(newUser);
then(userRepository).should().save(any(User.class));
then(emailService).should().sendWelcomeEmail("john@example.com");
then(auditService).should().logUserCreation(newUser.getId());
}
@Test
public void shouldThrowExceptionWhenEmailIsInvalid() {
// Given
given(emailService.isValidEmail("invalid-email")).willReturn(false);
// When/Then
assertThrows(InvalidEmailException.class, () -> {
userService.createUser("invalid-email", "John Doe");
});
then(userRepository).should(never()).save(any(User.class));
then(emailService).shouldHaveNoMoreInteractions();
}
}@Test
public void shouldProcessPaymentWithRetryLogic() {
// Given
PaymentRequest request = new PaymentRequest(100.00, "USD");
given(paymentGateway.processPayment(request))
.willThrow(TemporaryPaymentException.class) // First attempt fails
.willThrow(TemporaryPaymentException.class) // Second attempt fails
.willReturn(new PaymentResult("SUCCESS")); // Third attempt succeeds
// When
PaymentResult result = paymentService.processWithRetry(request);
// Then
then(result.getStatus()).isEqualTo("SUCCESS");
then(paymentGateway).should(times(3)).processPayment(request);
then(auditService).should().logPaymentRetry(request.getId(), 2);
then(notificationService).should().notifyPaymentSuccess(request.getId());
}Combine BDD Mockito with AssertJ for fluent assertions:
import static org.assertj.core.api.BDDAssertions.then;
import static org.mockito.BDDMockito.*;
@Test
public void shouldCalculateOrderTotal() {
// Given
Order order = new Order();
order.addItem(new OrderItem("item1", 10.00));
order.addItem(new OrderItem("item2", 15.00));
given(taxService.calculateTax(25.00)).willReturn(2.50);
given(discountService.calculateDiscount(order)).willReturn(5.00);
// When
OrderTotal total = orderService.calculateTotal(order);
// Then
then(total.getSubtotal()).isEqualTo(25.00);
then(total.getTax()).isEqualTo(2.50);
then(total.getDiscount()).isEqualTo(5.00);
then(total.getTotal()).isEqualTo(22.50);
then(taxService).should().calculateTax(25.00);
then(discountService).should().calculateDiscount(order);
}@Test
public void shouldApproveAccountWhenValidDocuments() {
// Given - Set up the scenario
Account account = new Account("ACC123");
List<Document> validDocuments = Arrays.asList(
new Document("ID", "valid"),
new Document("Proof of Address", "valid")
);
given(documentValidator.validateAll(validDocuments)).willReturn(true);
given(riskAssessment.evaluate(account)).willReturn(RiskLevel.LOW);
// When - Execute the behavior
ApprovalResult result = accountService.approve(account, validDocuments);
// Then - Verify the outcome
then(result.isApproved()).isTrue();
then(result.getReason()).isEqualTo("All validations passed");
then(documentValidator).should().validateAll(validDocuments);
then(riskAssessment).should().evaluate(account);
then(notificationService).should().notifyApproval(account.getId());
}// Good - describes behavior in business terms
@Test
public void shouldSendReminderEmailWhenSubscriptionExpiresInThreeDays() { }
@Test
public void shouldBlockTransactionWhenAmountExceedsDailyLimit() { }
@Test
public void shouldCreateAccountWithTrialPeriodForNewUsers() { }
// Avoid - technical implementation details
@Test
public void shouldCallEmailServiceSendMethod() { }
@Test
public void shouldReturnTrueFromValidateMethod() { }@Nested
@DisplayName("User Registration Scenarios")
class UserRegistrationScenarios {
@Test
@DisplayName("Given valid user data, when registering, then should create account and send welcome email")
public void validRegistration() {
// Given
UserRegistrationData userData = validUserData();
given(emailValidator.isValid(userData.getEmail())).willReturn(true);
given(userRepository.existsByEmail(userData.getEmail())).willReturn(false);
// When
RegistrationResult result = userService.register(userData);
// Then
then(result.isSuccessful()).isTrue();
then(emailService).should().sendWelcomeEmail(userData.getEmail());
}
@Test
@DisplayName("Given duplicate email, when registering, then should reject with appropriate error")
public void duplicateEmailRegistration() {
// Given
UserRegistrationData userData = validUserData();
given(userRepository.existsByEmail(userData.getEmail())).willReturn(true);
// When
RegistrationResult result = userService.register(userData);
// Then
then(result.isSuccessful()).isFalse();
then(result.getErrorMessage()).contains("Email already exists");
then(emailService).shouldHaveZeroInteractions();
}
}@Test
public void shouldProcessRefundRequest() {
// Given: A customer has made a purchase and requests a refund within the allowed period
Purchase purchase = new Purchase("ORDER123", 99.99, LocalDate.now().minusDays(5));
RefundRequest request = new RefundRequest(purchase.getId(), "Product damaged");
given(purchaseRepository.findById("ORDER123")).willReturn(purchase);
given(refundPolicy.isEligible(purchase, request)).willReturn(true);
given(paymentGateway.processRefund(purchase.getAmount())).willReturn(refundResult("SUCCESS"));
// When: The customer service processes the refund request
RefundResult result = refundService.processRefund(request);
// Then: The refund should be approved and processed
then(result.isApproved()).isTrue();
then(result.getAmount()).isEqualTo(99.99);
// And: All relevant services should be called
then(paymentGateway).should().processRefund(99.99);
then(notificationService).should().notifyRefundApproval(request);
}Install with Tessl CLI
npx tessl i tessl/maven-org-mockito--mockito-all