I think Uncle Bob does a great job explaining this in his Clean Code book (awesome read btw). But anyway, his point is that you should not be using statics anywhere you want to leverage polymorphism (which I think is precisely what you want for the above case).
In your case, you have a UserManager. By no means a complete application, right? You might have something more complicated that uses a UserManager. Let's say you have your own version of StackOverflow (don't do this of course, stackoverflow is awesome, no need to compete).
Okay, so we have a LoginService that calls UserManager.getUser(). This is an unchangeable dependecy (since we aren't leveraging polymorphism). If UserManager.getUser() requires an underlying SQL database, then guess what you need to run (or test) LoginService.... a SQL database!
public class LoginService {
public boolean authenticate(String username, String password) {
User user = UserManager.getUser(username); // hard dependency on implementation
// other stuff
}
}
The more prevalent solution is to abstract things that can change behind an interface. That way you can swap out implementations. LoginService has a job that should be tested and really shouldn't depend on a specific database implementation.
public interface UserManager {
User getUser(String id):
}
public class SQLUserManager implements UserManager {
@Override
public User getUser(String id) { // SQL stuff }
}
class LoginService {
public LoginService(UserManager userManager) {
this.userManager = userManager;
}
public boolean authenticate(String username, String password) {
User user = userManager.getUser(username);
// other stuff
}
}
Now LoginService can 1) be tested independently of what UserManager is used and 2) can be left alone if the user implementation changes.
It's not about mocking but testing your components without needing to setup an entire application stack.