Module 3: Mocking Review: TDD with Mockito
Module Overview
Review mocking techniques and learn how to apply them in the context of test-driven development using Mockito.
Learning Objectives
- Understand why mocking is essential for effective unit testing
- Review how to isolate components using mock objects
- Master the Mockito framework for creating mocks and stubs
- Creating mock objects with
Mockito.mock()
- Stubbing methods with
when()
andthenReturn()
- Verifying method calls with
verify()
- Using argument matchers like
any()
,eq()
- Creating mock objects with
- Apply TDD principles while utilizing mocks
- Writing tests first with mocked dependencies
- Implementing against the test specifications
- Refactoring while maintaining test coverage
- Mock different types of dependencies appropriately
- External services and APIs
- Databases and repositories
- Complex business logic components
- Use annotations to simplify mock creation
@Mock
for creating mock objects@InjectMocks
for injecting mocks into tested objects@Spy
for partial mocking
- Test exception handling using mocks
Unit Testing with Mocks
With Mockito, a mocking framework, we can write unit tests that utilize one type of test double called a mock.
Here is an excerpt from Martin Fowler's blog about this concept called test doubles:
- Dummy objects are passed around but never actually used. Usually they are just used to fill parameter lists.
- Fake objects actually have working implementations, but usually take some shortcut which makes them not suitable for production (an InMemoryTestDatabase is a good example).
- Stubs provide canned answers to calls made during the test, usually not responding at all to anything outside what's programmed in for the test.
- Spies are stubs that also record some information based on how they were called. One form of this might be an email service that records how many messages it was sent.
- Mocks are pre-programmed with expectations which form a specification of the calls they are expected to receive. They can throw an exception if they receive a call they don't expect and are checked during verification to ensure they got all the calls they were expecting.
We use mocks to test dependencies without the cost of that dependency! We don't actually want to make a call to AWS or, by golly, implement AWS... We want to mock it instead!
In this short lesson, you will learn how to mock a class, when to do so, and how to use mocks with dependency injection.
Code Example: Mocking with Mockito
import static org.mockito.Mockito.*;
import static org.junit.jupiter.api.Assertions.*;
// Example service to be tested
public class OrderService {
private final PaymentGateway paymentGateway;
private final OrderRepository orderRepository;
public OrderService(PaymentGateway paymentGateway, OrderRepository orderRepository) {
this.paymentGateway = paymentGateway;
this.orderRepository = orderRepository;
}
public OrderResult placeOrder(Order order) {
// Process payment
PaymentResult paymentResult = paymentGateway.processPayment(order.getAmount());
if (paymentResult.isSuccessful()) {
// Save order if payment successful
orderRepository.save(order);
return new OrderResult(true, "Order placed successfully");
} else {
return new OrderResult(false, "Payment failed: " + paymentResult.getMessage());
}
}
}
// Test using Mockito
@ExtendWith(MockitoExtension.class)
class OrderServiceTest {
@Mock
private PaymentGateway paymentGateway;
@Mock
private OrderRepository orderRepository;
@InjectMocks
private OrderService orderService;
@Test
void shouldPlaceOrderWhenPaymentSuccessful() {
// Arrange
Order order = new Order("123", 100.0);
PaymentResult paymentResult = new PaymentResult(true, "Payment successful");
when(paymentGateway.processPayment(100.0)).thenReturn(paymentResult);
// Act
OrderResult result = orderService.placeOrder(order);
// Assert
assertTrue(result.isSuccessful());
assertEquals("Order placed successfully", result.getMessage());
// Verify interactions
verify(paymentGateway).processPayment(100.0);
verify(orderRepository).save(order);
}
@Test
void shouldNotSaveOrderWhenPaymentFails() {
// Arrange
Order order = new Order("123", 100.0);
PaymentResult paymentResult = new PaymentResult(false, "Insufficient funds");
when(paymentGateway.processPayment(100.0)).thenReturn(paymentResult);
// Act
OrderResult result = orderService.placeOrder(order);
// Assert
assertFalse(result.isSuccessful());
assertEquals("Payment failed: Insufficient funds", result.getMessage());
// Verify interactions
verify(paymentGateway).processPayment(100.0);
verify(orderRepository, never()).save(any(Order.class));
}
}
Mastery Task 3: Writing Unit Tests with Mocks
Writing Unit Tests With Mocks
When writing unit tests, it is important to avoid calling any methods the belong to classes outside of the class we are testing. Since Spring requires coordination between several different layers even for the most basic API requests, mocking dependencies is very important.
For Spring, this means using the @MockBean
annotation to create a mock of any component we would otherwise be @Autowiring
in.
We can then use Mockito.when().thenReturn()
structure to return dummy data instead of calling the real method of a dependent class. We can also use Mockito.verify()
to check that a particular method did indeed get called. This is useful for void methods which have no return data, and thus we cannot see that they were called using Mockito.when().thenReturn()
.
Your task is to write unit tests for the CheckableService and achieve 95% or more code coverage. You will be given a jacoco gradle command to quickly test for code coverage, or you can select run with coverage on your test file to generate a detailed report (as an html file) under build/jacocoHtml.
Make sure to only @Autowire
the component under test! All other dependencies must be mocked. Your test code will be tested using Reflection again so please make sure to continue following the naming conventions. You can use the LibraryServiceTest as a reference.
Completion
Run the gradle command:
./gradlew -q clean test jacocoTestCoverage
and make sure all tests pass. (This will also test MT01 and MT02)