0
0
JUnittesting~15 mins

Why integration tests verify component interaction in JUnit - Automation Benefits in Action

Choose your learning style9 modes available
Verify interaction between UserService and UserRepository components
Preconditions (2)
Step 1: Create a new user object with name 'Alice' and email 'alice@example.com'
Step 2: Call UserService.createUser(user) to save the user
Step 3: Call UserService.getUserByEmail('alice@example.com') to retrieve the user
Step 4: Verify that the retrieved user is not null
Step 5: Verify that the retrieved user's name is 'Alice'
Step 6: Verify that the retrieved user's email is 'alice@example.com'
✅ Expected Result: UserService correctly saves and retrieves user data through UserRepository, confirming component interaction
Automation Requirements - JUnit 5
Assertions Needed:
assertNotNull for retrieved user
assertEquals for user name and email
Best Practices:
Use @BeforeEach to set up test data
Use dependency injection or constructor injection for UserService and UserRepository
Use in-memory database for isolation
Keep tests independent and repeatable
Automated Solution
JUnit
import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

class UserServiceIntegrationTest {

    private UserRepository userRepository;
    private UserService userService;

    @BeforeEach
    void setUp() {
        userRepository = new InMemoryUserRepository(); // simple in-memory implementation
        userService = new UserService(userRepository);
    }

    @Test
    void testCreateAndRetrieveUser() {
        User user = new User("Alice", "alice@example.com");
        userService.createUser(user);

        User retrieved = userService.getUserByEmail("alice@example.com");

        assertNotNull(retrieved, "Retrieved user should not be null");
        assertEquals("Alice", retrieved.getName(), "User name should match");
        assertEquals("alice@example.com", retrieved.getEmail(), "User email should match");
    }
}

// Supporting classes for completeness
class User {
    private final String name;
    private final String email;

    public User(String name, String email) {
        this.name = name;
        this.email = email;
    }

    public String getName() { return name; }
    public String getEmail() { return email; }
}

interface UserRepository {
    void save(User user);
    User findByEmail(String email);
}

class InMemoryUserRepository implements UserRepository {
    private final Map<String, User> storage = new HashMap<>();

    @Override
    public void save(User user) {
        storage.put(user.getEmail(), user);
    }

    @Override
    public User findByEmail(String email) {
        return storage.get(email);
    }
}

class UserService {
    private final UserRepository userRepository;

    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    public void createUser(User user) {
        userRepository.save(user);
    }

    public User getUserByEmail(String email) {
        return userRepository.findByEmail(email);
    }
}

This test verifies the interaction between UserService and UserRepository. The @BeforeEach method sets up a fresh in-memory repository and service before each test to keep tests independent.

The test testCreateAndRetrieveUser creates a user, saves it via UserService, then retrieves it by email. Assertions check that the retrieved user is not null and that the name and email match the input.

This confirms that UserService correctly uses UserRepository to store and fetch data, which is the core purpose of integration testing: verifying that components work together as expected.

Common Mistakes - 3 Pitfalls
Testing UserService methods without verifying UserRepository interaction
Using a real database instead of an in-memory database for integration tests
Not resetting data between tests
Bonus Challenge

Now add data-driven testing with 3 different user inputs to verify component interaction for multiple cases

Show Hint