Avoiding Real External Calls by Replacing Dependencies with Mocks
Introduction
When writing unit tests, one of the biggest challenges is handling dependencies on external systems. For example, consider a UserService class that sends emails through an external EmailService. While this design works in production, it becomes problematic in a test environment. Running tests should be fast, reliable, and independent of outside systems. However, if the real EmailService is called, the test may send actual emails, consume unnecessary resources, and depend on network availability. To solve this issue, developers rely on dependency injection (DI) and mocking. Dependency injection allows us to pass a different implementation of a service into a class, while mocking provides a fake version of the external service that behaves like the original but does not perform real actions.

At its core, dependency injection means that a class should not create its own dependencies. Instead, dependencies should be provided externally, typically via the constructor or setter methods. In our case, UserService depends on EmailService. If UserService creates its own instance of EmailService, then it is tightly coupled to the real implementation, making testing difficult. But if we design UserService to accept an EmailService as a parameter, we can inject either the real service (in production) or a mock service (in tests).
Here’s a simple design:
public class UserService {
private final EmailService emailService;
// Constructor injection
public UserService(EmailService emailService) {
this.emailService = emailService;
}
public void registerUser(String userEmail) {
// Business logic for user registration
emailService.sendEmail(userEmail, "Welcome to our platform!");
}
}
In the example above, UserService does not create its own EmailService. Instead, it receives one through the constructor. During production, we can inject the real service, but during testing, we inject a mock.
Now, when writing unit tests, instead of using the real service, we create a mock implementation of EmailService:
public class MockEmailService implements EmailService {
private boolean emailSent = false;
@Override
public void sendEmail(String recipient, String message) {
emailSent = true; // Record that the method was called
}
public boolean isEmailSent() {
return emailSent;
}
}

In the test:
@Test
public void testUserRegistrationSendsEmail() {
MockEmailService mockEmailService = new MockEmailService();
UserService userService = new UserService(mockEmailService);
userService.registerUser("test@example.com");
assertTrue(mockEmailService.isEmailSent());
}
Here, no real email is sent. The test simply checks whether the sendEmail method was called, ensuring that UserService behaves correctly. This is the essence of using dependency injection with mocks: isolating the class under test and verifying behavior without relying on external systems.
Many modern testing frameworks like Mockito in Java, unittest.mock in Python, or Moq in .NET simplify this process by automatically creating mocks. Instead of writing a MockEmailService, we can generate one at runtime and set expectations about how it should behave.

Conclusion
Dependency injection combined with mocking provides a clean and effective way to test classes like UserService that rely on external systems such as EmailService. By injecting dependencies instead of hardcoding them, we gain flexibility to swap real implementations with mocks in testing environments. This leads to faster, more reliable, and more maintainable tests, since they run independently of external services. Ultimately, adopting this approach helps ensure that unit tests remain true to their purpose: testing the logic of a class in isolation.