To increase abstraction so you can, e.g., swap databases out.
It certainly makes it easier to test because you can inject a fake DB.
But the real value comes when you need to switch DBs in the prod application.
Instead of relying on a SQL Server DB, you could switch to a Postgre SQL DB. Or even a Mongo DB, although you'd need a lot more work for that since SQL to noSQL is usually a lot of work simply because of the paradigm shift.
If you purely do IoC for testing, some things won't be injected that should be, and some things will be that shouldn't (although this is a pretty rare case, it does happen sometimes).
IoC done right will make it easier to test, but you shouldn't do it just for testing.
If you do it just for testing, you quickly fall into the "every type needs an interface" trap.
Which makes it harder to make changes.
Two obvious reasons are 1: you'll never be consistent about what to use--interface or class.
And 2: you'll end up relying on a type's specific implementation in call sites where you are using the interface.
So your interface might be called IDb
, for example, but places will rely on you only having one implementation: the ICsvDb
.
So when you end up switching to a SQL DB or an XLSX DB down the road, you'll break places you thought were save because they were using the IDb
when they were so tightly coupled to the one implementation they should've used it directly.
And if you do use it directly, then you very quickly fall into the #1 trap--no consistency.
Of course, if you don't do IoC correctly, you'll find that it's a massive pain to test anything that uses the CsvDb
type downstream because it does file IO.
Which is what often leads to that one-type/one-interface problem.
The correct way to solve this is to abstract out the IO--not the whole type.
So the CsvDb
type should inject an IFileProvider
that handles IO for you. And now you can use your CsvDb
type safely downstream without affecting tests.
And you can swap out OS-specific IO, for example, if necessary.