My experience is different. Inversion of control is a pattern that works just about anywhere. Javascript, C, Kotlin, Ruby, etc. You name it. I've dealt with a lot of code bases where writing tests was hard because the developers did not understand this simple design pattern. E.g. a lot of javascript frontend code ends up being hard to test for this reason. Anytime you mix object creation and logic, you make it harder to unit test.
In Java, using frameworks for this is common mainly because it has reflection and annotations, which means you don't actually have to manually call a lot of things to get your objects. E.g. in Spring, all you need to do is slap the annotation @Component on your component classes and it sort of self assembles the object graph from just that information. It's kind of neat if you do this properly. There are tons of ways to customise that or do it differently but it can be pretty minimalistic.
You can do this without frameworks as well (lookup DIY dependency injection). It basically just means that you write the code that constructs your objects yourself and do the right thing of not putting that in the wrong place (which results in hard to test code).
There are only two simple rules you need to remember: constructors must not do work (like constructing other objects or initializing some middleware) and all dependencies come in as constructor arguments. If you do that consistently, you are doing DIY dependency injection. Any time that looks tedious, you are likely violating some of the SOLID principles (e.g. because your constructor has 10 dependencies and the whole class suffers from poor cohesiveness).
In Kotlin, the trend is to move away from using reflection or annotation towards using more explicit DSLs with helper functions that figure out how to construct stuff at compile time (by using the type system and some language features). For example, KOIN https://github.com/InsertKoinIO/koin is a framework for Kotlin that uses neither reflection nor annotations. The same principles are used in Kofu, which is a kotlin centric way of doing similar things for Spring. Aside from being easier to debug (both of these use simple function calls), it also has the side effect of enabling better startup performance as well as native compilation using e.g. Graal.
Whichever way you do this, IOC ready components are easy to unit test because nothing gets constructed as a side effect of constructing them. Side effect free construction might be a better term. I was recently working on some python code that was the opposite where db initialization happened as a side effect of importing some module that exposed a global variable. I would call that broken by design. Python has all the tools you need to do better than that. There's no technical need to write broken code like that. I've seen similar problems in javascript code bases for frontend as well as node.js. Usually if you find a system that is hard to unit test, this is the root cause.