This is highly dependent on your language. A language where functions can be passed as values allows functions to be injected as parameters (for polymorphic behavior). In Clojure, you can even replace a function definition just for unit tests without dependency injection at all.
For example, with-redefs. This allows you to replace a given function anywhere in the whole codebase with any lambda. This rebinding only lasts inside the scope of the s-expression. When I realized that, I realized everything I knew about DI and interfaces were just hacks of incredible complexity to get around a poor language that wasn't built to allow unit testing. I now think it's impossible to have both unit testing and a good design in Java/C#. Neither were made to be easy to unit test, it wasn't even a concept when Java was made.
In C#'s defense, there is a way to mock a static function by replacing it using the Mocks library. To MS's shame, that library requires the 13k edition of Visual Studio. When I realized that, I realized I want to leave C# for good. I'm not interested anymore in a language that resists testing so much I have to choose between an easy to understand design and unit tests.