0
# BDD-Style Testing
1
2
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.
3
4
## BDD Stubbing
5
6
### Given-Will Pattern
7
8
Replace `when().thenReturn()` with `given().willReturn()`:
9
10
```java { .api }
11
public static <T> BDDMyOngoingStubbing<T> given(T methodCall);
12
13
public interface BDDMyOngoingStubbing<T> {
14
BDDMyOngoingStubbing<T> willReturn(T value);
15
BDDMyOngoingStubbing<T> willReturn(T value, T... values);
16
BDDMyOngoingStubbing<T> willThrow(Throwable... throwables);
17
BDDMyOngoingStubbing<T> willThrow(Class<? extends Throwable> throwableType);
18
BDDMyOngoingStubbing<T> willAnswer(Answer<?> answer);
19
BDDMyOngoingStubbing<T> willCallRealMethod();
20
T getMock();
21
}
22
```
23
24
**Traditional Mockito vs BDD Style:**
25
26
```java
27
// Traditional Mockito style
28
@Test
29
public void shouldReturnUserWhenValidId() {
30
// Setup
31
when(userRepository.findById(123)).thenReturn(user);
32
33
// Execute
34
User result = userService.getUser(123);
35
36
// Verify
37
assertEquals(user, result);
38
}
39
40
// BDD style
41
@Test
42
public void shouldReturnUserWhenValidId() {
43
// Given
44
given(userRepository.findById(123)).willReturn(user);
45
46
// When
47
User result = userService.getUser(123);
48
49
// Then
50
assertEquals(user, result);
51
}
52
```
53
54
### BDD Stubbing Methods
55
56
```java { .api }
57
public static <T> BDDStubber willReturn(T value);
58
public static BDDStubber willThrow(Throwable... throwables);
59
public static BDDStubber willThrow(Class<? extends Throwable> throwableType);
60
public static BDDStubber willAnswer(Answer<?> answer);
61
public static BDDStubber willDoNothing();
62
public static BDDStubber willCallRealMethod();
63
```
64
65
**Usage Examples:**
66
67
```java
68
// Return values
69
given(calculator.add(2, 3)).willReturn(5);
70
given(userService.findByEmail("user@example.com")).willReturn(user);
71
72
// Multiple return values
73
given(randomService.nextInt()).willReturn(1, 2, 3);
74
75
// Throw exceptions
76
given(paymentService.processPayment(invalidCard)).willThrow(PaymentException.class);
77
given(databaseService.connect()).willThrow(new SQLException("Connection failed"));
78
79
// Custom answers
80
given(timeService.getCurrentTime()).willAnswer(invocation -> Instant.now());
81
82
// Call real method (for spies)
83
given(spy.calculate(anyInt())).willCallRealMethod();
84
```
85
86
## BDD Void Method Stubbing
87
88
### Will-Do Pattern for Void Methods
89
90
Use the will-do pattern for stubbing void methods:
91
92
```java
93
// Traditional style
94
doThrow(new RuntimeException()).when(emailService).sendEmail(anyString());
95
doNothing().when(auditService).log(anyString());
96
97
// BDD style
98
willThrow(new RuntimeException()).given(emailService).sendEmail(anyString());
99
willDoNothing().given(auditService).log(anyString());
100
```
101
102
**Complete Example:**
103
104
```java
105
@Test
106
public void shouldHandleEmailFailure() {
107
// Given
108
willThrow(EmailException.class).given(emailService).sendEmail(anyString());
109
110
// When
111
assertThrows(EmailException.class, () -> {
112
userService.registerUser("john@example.com", "John");
113
});
114
115
// Then
116
verify(auditService).logError(contains("Email sending failed"));
117
}
118
```
119
120
## BDD Verification
121
122
### Then-Should Pattern (v1.10.0+)
123
124
Use `then()` for BDD-style verification:
125
126
```java { .api }
127
public static <T> Then<T> then(T mock);
128
129
public interface Then<T> {
130
T should();
131
T should(VerificationMode mode);
132
T shouldHaveNoMoreInteractions();
133
T shouldHaveZeroInteractions();
134
}
135
```
136
137
**Usage Examples:**
138
139
```java
140
// Traditional verification
141
verify(emailService).sendEmail("user@example.com");
142
verify(auditService, times(2)).log(anyString());
143
verifyNoMoreInteractions(emailService);
144
145
// BDD verification
146
then(emailService).should().sendEmail("user@example.com");
147
then(auditService).should(times(2)).log(anyString());
148
then(emailService).shouldHaveNoMoreInteractions();
149
```
150
151
## Complete BDD Test Structure
152
153
### Full Given/When/Then Example
154
155
```java
156
@RunWith(MockitoJUnitRunner.class)
157
public class UserServiceBDDTest {
158
159
@Mock private UserRepository userRepository;
160
@Mock private EmailService emailService;
161
@Mock private AuditService auditService;
162
163
@InjectMocks private UserService userService;
164
165
@Test
166
public void shouldCreateUserAndSendWelcomeEmail() {
167
// Given
168
User newUser = new User("john@example.com", "John Doe");
169
given(userRepository.save(any(User.class))).willReturn(newUser);
170
given(emailService.isValidEmail("john@example.com")).willReturn(true);
171
172
// When
173
User result = userService.createUser("john@example.com", "John Doe");
174
175
// Then
176
then(result).isEqualTo(newUser);
177
then(userRepository).should().save(any(User.class));
178
then(emailService).should().sendWelcomeEmail("john@example.com");
179
then(auditService).should().logUserCreation(newUser.getId());
180
}
181
182
@Test
183
public void shouldThrowExceptionWhenEmailIsInvalid() {
184
// Given
185
given(emailService.isValidEmail("invalid-email")).willReturn(false);
186
187
// When/Then
188
assertThrows(InvalidEmailException.class, () -> {
189
userService.createUser("invalid-email", "John Doe");
190
});
191
192
then(userRepository).should(never()).save(any(User.class));
193
then(emailService).shouldHaveNoMoreInteractions();
194
}
195
}
196
```
197
198
### Complex BDD Scenario
199
200
```java
201
@Test
202
public void shouldProcessPaymentWithRetryLogic() {
203
// Given
204
PaymentRequest request = new PaymentRequest(100.00, "USD");
205
given(paymentGateway.processPayment(request))
206
.willThrow(TemporaryPaymentException.class) // First attempt fails
207
.willThrow(TemporaryPaymentException.class) // Second attempt fails
208
.willReturn(new PaymentResult("SUCCESS")); // Third attempt succeeds
209
210
// When
211
PaymentResult result = paymentService.processWithRetry(request);
212
213
// Then
214
then(result.getStatus()).isEqualTo("SUCCESS");
215
then(paymentGateway).should(times(3)).processPayment(request);
216
then(auditService).should().logPaymentRetry(request.getId(), 2);
217
then(notificationService).should().notifyPaymentSuccess(request.getId());
218
}
219
```
220
221
## BDD with AssertJ
222
223
Combine BDD Mockito with AssertJ for fluent assertions:
224
225
```java
226
import static org.assertj.core.api.BDDAssertions.then;
227
import static org.mockito.BDDMockito.*;
228
229
@Test
230
public void shouldCalculateOrderTotal() {
231
// Given
232
Order order = new Order();
233
order.addItem(new OrderItem("item1", 10.00));
234
order.addItem(new OrderItem("item2", 15.00));
235
236
given(taxService.calculateTax(25.00)).willReturn(2.50);
237
given(discountService.calculateDiscount(order)).willReturn(5.00);
238
239
// When
240
OrderTotal total = orderService.calculateTotal(order);
241
242
// Then
243
then(total.getSubtotal()).isEqualTo(25.00);
244
then(total.getTax()).isEqualTo(2.50);
245
then(total.getDiscount()).isEqualTo(5.00);
246
then(total.getTotal()).isEqualTo(22.50);
247
248
then(taxService).should().calculateTax(25.00);
249
then(discountService).should().calculateDiscount(order);
250
}
251
```
252
253
## BDD Best Practices
254
255
### Clear Test Structure
256
257
```java
258
@Test
259
public void shouldApproveAccountWhenValidDocuments() {
260
// Given - Set up the scenario
261
Account account = new Account("ACC123");
262
List<Document> validDocuments = Arrays.asList(
263
new Document("ID", "valid"),
264
new Document("Proof of Address", "valid")
265
);
266
given(documentValidator.validateAll(validDocuments)).willReturn(true);
267
given(riskAssessment.evaluate(account)).willReturn(RiskLevel.LOW);
268
269
// When - Execute the behavior
270
ApprovalResult result = accountService.approve(account, validDocuments);
271
272
// Then - Verify the outcome
273
then(result.isApproved()).isTrue();
274
then(result.getReason()).isEqualTo("All validations passed");
275
276
then(documentValidator).should().validateAll(validDocuments);
277
then(riskAssessment).should().evaluate(account);
278
then(notificationService).should().notifyApproval(account.getId());
279
}
280
```
281
282
### Descriptive Test Names
283
284
```java
285
// Good - describes behavior in business terms
286
@Test
287
public void shouldSendReminderEmailWhenSubscriptionExpiresInThreeDays() { }
288
289
@Test
290
public void shouldBlockTransactionWhenAmountExceedsDailyLimit() { }
291
292
@Test
293
public void shouldCreateAccountWithTrialPeriodForNewUsers() { }
294
295
// Avoid - technical implementation details
296
@Test
297
public void shouldCallEmailServiceSendMethod() { }
298
299
@Test
300
public void shouldReturnTrueFromValidateMethod() { }
301
```
302
303
### Scenario-Based Testing
304
305
```java
306
@Nested
307
@DisplayName("User Registration Scenarios")
308
class UserRegistrationScenarios {
309
310
@Test
311
@DisplayName("Given valid user data, when registering, then should create account and send welcome email")
312
public void validRegistration() {
313
// Given
314
UserRegistrationData userData = validUserData();
315
given(emailValidator.isValid(userData.getEmail())).willReturn(true);
316
given(userRepository.existsByEmail(userData.getEmail())).willReturn(false);
317
318
// When
319
RegistrationResult result = userService.register(userData);
320
321
// Then
322
then(result.isSuccessful()).isTrue();
323
then(emailService).should().sendWelcomeEmail(userData.getEmail());
324
}
325
326
@Test
327
@DisplayName("Given duplicate email, when registering, then should reject with appropriate error")
328
public void duplicateEmailRegistration() {
329
// Given
330
UserRegistrationData userData = validUserData();
331
given(userRepository.existsByEmail(userData.getEmail())).willReturn(true);
332
333
// When
334
RegistrationResult result = userService.register(userData);
335
336
// Then
337
then(result.isSuccessful()).isFalse();
338
then(result.getErrorMessage()).contains("Email already exists");
339
then(emailService).shouldHaveZeroInteractions();
340
}
341
}
342
```
343
344
### Integration with Cucumber-Style Comments
345
346
```java
347
@Test
348
public void shouldProcessRefundRequest() {
349
// Given: A customer has made a purchase and requests a refund within the allowed period
350
Purchase purchase = new Purchase("ORDER123", 99.99, LocalDate.now().minusDays(5));
351
RefundRequest request = new RefundRequest(purchase.getId(), "Product damaged");
352
353
given(purchaseRepository.findById("ORDER123")).willReturn(purchase);
354
given(refundPolicy.isEligible(purchase, request)).willReturn(true);
355
given(paymentGateway.processRefund(purchase.getAmount())).willReturn(refundResult("SUCCESS"));
356
357
// When: The customer service processes the refund request
358
RefundResult result = refundService.processRefund(request);
359
360
// Then: The refund should be approved and processed
361
then(result.isApproved()).isTrue();
362
then(result.getAmount()).isEqualTo(99.99);
363
364
// And: All relevant services should be called
365
then(paymentGateway).should().processRefund(99.99);
366
then(notificationService).should().notifyRefundApproval(request);
367
}
368
```