A fair question indeed, and one that I did not demonstrate above in my examples. Since the constructor is not accessible to us, even in a test, we are unable to change how the object is created and can't get the benefit of dependency injection (dev.to/kylec32/effective-java-tues...). While none of the above examples show any places where that would be a problem, many singletons in the wild do have this problem if they have any external dependencies (DB, network, other components, etc). So the more I think of it, it's likely the lack of ability to use dependency injection that is the true problem here.
Compare that to a singleton that is just a collection of pure functions that would indeed be testable (but at that point you are more looking at a Utility class dev.to/kylec32/effective-java-tues...)
"They are extremely hard to test" - why?
A fair question indeed, and one that I did not demonstrate above in my examples. Since the constructor is not accessible to us, even in a test, we are unable to change how the object is created and can't get the benefit of dependency injection (dev.to/kylec32/effective-java-tues...). While none of the above examples show any places where that would be a problem, many singletons in the wild do have this problem if they have any external dependencies (DB, network, other components, etc). So the more I think of it, it's likely the lack of ability to use dependency injection that is the true problem here.
Compare that to a singleton that is just a collection of pure functions that would indeed be testable (but at that point you are more looking at a Utility class dev.to/kylec32/effective-java-tues...)
I think you're right about Dependency Injection for unit testing with only, say, JUnit 5.
However, this is where I have found mocking libraries such as Mockito benefit the tests, as the coupled logic can be stubbed out.
(Thanks for the detailed reply 👍 )
Edit: Your second link doesn't appear to be working?