Having a background in compiled languages like C# and C++, I am familiar with the concept of creating hard dependencies when importing types from other namespaces or assemblies using using
or #include
statements. This is why relying on interfaces and IoC containers becomes crucial for achieving testability and maintainability.
Transitioning to developing my first project in TypeScript, specifically an Express backend, made me apprehensive about using imports extensively as I feared they would create hard dependencies and hinder testability. To address this concern, I designed a code architecture that allowed for easy swapping of module dependencies by only importing types/interfaces and resolving them with tsyringe (I chose tsyringe but any container could be used). While this approach led to more boilerplate code, it facilitated smooth mocking of internal dependencies.
During my exploration, I came across a NDC talk by Rob Richardson discussing mocking in TypeScript tests. He introduced two TypeScript libraries: ts-auto-mock and ts-mock-imports. While ts-auto-mock wasn't anything groundbreaking, handling interface mocking was challenging. However, ts-mock-imports allows us to replace imports from different modules with mocks, providing a comprehensive solution for testing code effectively. This raised doubts in my mind regarding the necessity of IoC containers in TypeScript when tools like ts-mock-imports are available.
The question of code maintainability still remains, where IoC containers might offer assistance, although their relevance in TypeScript is uncertain. In my project, I primarily utilized the IoC container to enhance testability.
Considering scenarios in TypeScript where IoC containers can simplify tasks beyond enhancing testability, and whether dependency injection is essential when we have the capability to mock imports at will, becomes a thought-provoking inquiry.