When you really need to test legacy code, you might very quickly discover that it's impossible. Even a simple function calls another function, which calls yet another function, and this one even stores results in the database before returning the result. When you test object-oriented code, it might not be too bad; you can stub and mock objects to replace unwanted dependencies and be able to focus on the tested piece of code.
But of course, there is a way to test the untestable, and we will see some libraries and extensions that might help us. But please, always try to keep things simple. Even test doubles can be a double-edged sword—when replacing actual code, you might miss real bugs.
The mentioned libraries and extensions ...