Java
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented Java
-
Section 4: Collections Framework
-
Section 5: Exception Handling
-
Section 6: Generics
-
Section 7: Functional Java
-
Section 8: Concurrency
-
Section 9: I/O and NIO
-
Section 10: JVM Internals
-
Section 11: Modern Java Features
-
Section 12: Build Tools and Project Structure
-
Section 13: Testing
-
Section 14: Databases and Persistence
-
Section 15: Networking
-
Section 16: Design and Best Practices
-
Section 17: Reflection and Annotations
-
Section 18: Logging and Diagnostics
-
Section 19: Date, Time, and Internationalization
-
Section 20: Java Platform Module System
-
Section 21: Security in Java
-
Section 22: Advanced Collections and Data Structures
-
Section 23: More Concurrency Patterns
-
Section 24: Compression, Files, and System Integration
-
Section 25: GUI Programming
-
Section 26: Practical Projects
-
Section 27: Data Structures and Algorithms
-
Section 28: Interview and Algorithm Practice
-
Section 29: JSON and Data Interchange
-
Section 30: More Concurrency Utilities
-
Section 31: More Collections and Streams Practice
-
Section 32: More File and System Programming
-
Section 33: Standard Library Deep Dive
-
Section 34: More Practice and Drills
-
Section 35: More Testing and Quality
-
Section 36: More Design Patterns and Architecture
-
Section 37: Career and Ecosystem
-
Section 38: More OOP and Architecture Practice
-
Section 39: More Enterprise Concepts
-
Section 40: Advanced JavaFX
-
Section 41: More Interview Practice
127: Integration Testing Basics
I want to show you something that happened to me a few years ago. I had a feature—a simple user registration system—and my unit tests were all green. I used Mockito to mock the database layer, and the UserService behaved exactly as I expected. I pushed to staging, and within ten minutes, the logs were screaming about DataIntegrityViolationException. I was baffled. The logic was right; the mocks were right. So why was it breaking?
The Lie of the Green Checkmark
Let's recreate that scenario. Imagine we have a UserRepository that handles the SQL and a UserService that checks if an email is taken before saving. In a unit test, I’d do something like this:
@Test
void shouldSaveUserWhenEmailIsUnique() {
// Mocking the repo to just say "Nope, this email isn't taken"
when(userRepository.existsByEmail("test@example.com")).thenReturn(false);
userService.registerUser(new User("test@example.com"));
verify(userRepository).save(any(User.class));
}
This test passes. It's fast. It's clean. But here is the problem: the mock is a liar. It tells the UserService exactly what it wants to hear. It doesn't know that the actual database has a UNIQUE constraint on the email column, or that my SQL dialect is slightly different from what the JPA provider expects. I'm testing my assumption of how the database works, not the database itself.
Connecting the Actual Wires
To fix this, I need to stop pretending. I need an integration test. Instead of mocking the UserRepository, I'm going to let Spring actually boot up a slice of the application and connect to a real (though in-memory) database like H2. I'll swap @ExtendWith(MockitoExtension.class) for @SpringBootTest.
I'll try to run the same registration logic, but this time, I'll try to register the same email twice in a row. In my unit tests, I could just force the mock to return true the second time. But here, I want to see if the database actually throws the exception I expect.
@SpringBootTest
class UserIntegrationTest {
@Autowired
private UserService userService;
@Test
void shouldThrowExceptionWhenDatabaseConstraintIsViolated() {
User user = new User("duplicate@example.com");
userService.registerUser(user);
// Now we try to save the exact same user again
assertThrows(DataIntegrityViolationException.class, () -> {
userService.registerUser(new User("duplicate@example.com"));
});
}
}
When I run this, it's slower. I can feel the JVM churning as the ApplicationContext loads. But suddenly, I see a failure. Not a logic failure, but a configuration failure: the H2 database isn't configured to be case-insensitive for emails, but my production Postgres DB is. My "unique" check is passing in the test but will fail in production. I just found a bug that a thousand unit tests would have missed.
The Messy Reality of State
Now I've hit a new problem. I run this test twice, and the second time it fails because the user from the first test is still sitting in the database. Unit tests are stateless; integration tests are not. They leave footprints.
I could manually delete the users in a tearDown method, but that's tedious and error-prone. Instead, I'll use @Transactional on the test method. In Spring, this tells the framework to wrap the entire test in a transaction and then roll it back once the test finishes. It's like the test happened in a parallel universe that gets erased the moment the assertion is done.
@Test
@Transactional
void shouldKeepDatabaseClean() {
userService.registerUser(new User("clean@example.com"));
// This user will be rolled back automatically after the test
}
This is the core of integration testing: you move away from "Does this method do X?" toward "Does this system actually work when the pieces are wired together?" You trade speed for confidence. You don't need an integration test for every edge case—that's what unit tests are for—but you absolutely need them for the boundaries where your code touches the outside world.
📋 Practical Task
Fixing the Order Repository Constraint
You have an OrderService and an OrderRepository. The Order entity has a orderNumber field that must be unique. You have a unit test using mocks that passes, but the system crashes in production when duplicate order numbers are submitted because the OrderService is failing to catch the database's UniqueConstraintViolation.
Your Task:
- Create an integration test class
OrderIntegrationTestannotated with@SpringBootTest. - Inject the real
OrderService. - Write a test method annotated with
@Transactionalthat attempts to save twoOrderobjects with the sameorderNumber. - Use
assertThrowsto verify that the database actually throws aDataIntegrityViolationException(or the specific persistence exception used in your environment) when the second order is saved. - Ensure the test proves that the database constraint is active and functioning, regardless of what the service-level mocks previously suggested.
There are no comments for now.