The key of DI containers is the abstraction. DI containers abstract this design concern for you. As you may guess, it has a noticeable impact on the code, often translated into a fewer number of constructors, setters, factories and builders. In consequence, they make your code more loosely coupled (due to the underlying IoC), cleaner and simpler. Although not without a cost.
Years ago, such abstraction required us to write various config files (declarative programming). It was fantastic to see the number of LOC diminishing substantially, but while the LOCSs decreased, the number of config files slightly increased. For medium or small projects it was not a problem at all, but for larger projects, to have the "assembly of the code" scattered 50% between code and XML descriptors became a pain in the ...
Nonetheless, the main problem was the paradigm itself. It was quite inflexible because the descriptors left little space for customizations.
The paradigm shifted progressively towards to convention over configuration, which trades config files for annotations or attributes. It keeps our code cleaner and simpler while providing us with the flexibility that XML could not.
Probably, this is the most significant difference regarding with how you were working until now. Less code and more magic.
On the down side...
Convention over configuration makes the abstraction so heavy that you barely know how it works. Sometimes seems magic and here comes one more drawback. Once is working, many developers don't care about how it works.
This is a handicap for those who learnt DI with DI containers. They don't fully understand the relevance of the design patterns, the good practices and the principles that were abstracted by the container. I have asked developers if they were familiarised with DI and its advantages and they answered: -Yes, I have used Spring IoC-. (What the hell does it mean?!?)
One can agree or not with each of these "drawbacks". In my humble opinion, it's a must to know what's going on in your project. Otherwise, we don't have a full understanding of it. Also is to know what DI is for and to have a notion of how to implement it is a plus to consider.
On the plus side...
One word productivity. Frameworks let us focus on what really matters. The application's business.
Despite the commented shortcomings, for many of us (which job is conditioned by deadlines and costs), these tools are an invaluable resource. In my opinion, this should be our main argument in favour of implementing a DI containers. Productivity.
Whether if we implement pure DI or containers, testing is not going to be a problem. Quite the opposite. The same containers often provide us with the mocks for unit testing out of the box. Along the years I have used my tests as thermometers. They tell me if I have relied heavily on the containers facilities. I say this because these containers can initialize private attributes without setters or constructors. They can inject components almost in any place!
Sounds tempting right? Be careful! Don't fall into the trap!!
If you decide to implement containers, I strongly suggest sticking with the good practices. Keep implementing constructors, setters and interfaces. Enforce the framework/container to use them. It will make easier possible migrations to another framework or to remove the actual one. It can reduce significantly the dependency on the container.
Regarding this practices:
When to use DI containers
My answer is going to be biased by own experience which is mainly in favour of DI containers (as I commented, I'm heavily focused on productivity, so I have never had the need of pure DI. Quite the opposite).
I think you will find interesting Mark Seeman's article about this subject, which also answers the question regarding how to implement pure DI.
Finally, if we were speaking of Java, I would consider first taking a look to the JSR330 - Dependency Injection.
- Cost-cutting and time-saving. Productivity.
- A smaller number of components.
- The code becomes simpler and cleaner.
- DI is delegated to 3rd party libs. We should be aware of their trade-offs, strengths and weakness.
- Learning curve.
- They quickly make you forget some good habits.
- Increase of the project's dependencies (more libs)
- Containers based on reflection require more resources (memory). This is important if we are constrained by the resources.
Differences with pure DI:
- Less code and more configuration files or annotations.