Home / Blog / News / How dependency injection rescued my app from the untestable abyss – Insights from Laurent Cazanove’s Vue.js Nation 2025 Talk
How dependency injection rescued my app from the untestable abyss – Insights from Laurent Cazanove’s Vue.js Nation 2025 Talk

How dependency injection rescued my app from the untestable abyss – Insights from Laurent Cazanove’s Vue.js Nation 2025 Talk

Eleftheria Batsou
Eleftheria Batsou
Updated: March 17th 2025
This entry is part 4 of 7 in the series Vue.js Nation 2025 Talks

This article is based on Laurent Cazanove's talk at Vue.js Nation 2025, titled "How dependency injection rescued my app from the untestable abyss." In this session, Laurent discussed the challenges of leaking implementation details, bloated composables, and untestable code—a situation many developers find themselves in. What began as a "keep it simple" approach turned into a complex web of business logic, API calls, and retry mechanisms.

In Vue.js Nation 2025 Laurent shared insights on utilizing Nuxt plugins to implement dependency injection as a strategy to clean up architecture, decouple logic, and simplify testing processes without falling into the trap of over-engineering.

Challenges of Tightly Coupled Code

Laurent shared his experience with a legacy app running on Firebase, which he wanted to migrate to Superbase. Despite having good integration tests, he found that his business logic was too tightly coupled with Firebase code. This coupling led to leaking implementation details and bloated composable code.

The tightly coupled codebase made the application difficult to test and maintain. It was challenging to identify a starting point for migration.

"I had good integrations test using Firebase, but, my business logic was just, like, too tightly coupled with the Firebase code. […] It was really hard to know where to start for this migration, and what felt particularly bad was that there was no way for me to leverage my tests to help in that migration." ~ Laurent

Building Testable Apps with Dependency Injection

In his talk, Laurent shared a key solution: dependency injection. Dependency injection helps in building testable apps by cleaning up composables, decoupling business logic from dependencies, and writing more testable code.

Key Learnings

  • Cleaning up Composables: By using dependency injection, you can streamline your components and reduce unnecessary complexity.
  • Decoupling Business Logic: Separating your app's core logic from dependencies allows for easier maintenance and updates.
  • Writing Testable Code: Dependency injection makes it simpler to write tests by providing mock dependencies for testing purposes.

Example: A UserList Component

Laurent introduces a simple example of a UserList component.

The component utilizes a getUsers function provided by the useUsers composable. This function is employed within an asynchronous data hook to handle data fetching efficiently.

Laurent.png

Using Supabase Client:

The useUsers composable relies on the Supabase client for its operations:

  • Supabase Integration: The getUsers function retrieves users from the database using Supabase.
  • Composable Simplicity: The composable returns this function so that various components can utilize it seamlessly.
Laurent-3.png

This example highlights how dependency injection can streamline your code by allowing components to easily access shared functionality without tightly coupling them to specific implementations.

Anatomy of a Test in Nuxt

Testing a component in Nuxt involves several steps. First, import the necessary modules. Then, define the test case to check specific functionalities.

Challenges with Mocking

One common challenge is deciding what to mock in tests. For instance, instead of using an actual database like Supabase during tests, you might consider using an in-memory database to speed up local testing. However, simply mocking functions like useUsers might lead to repetitive mock implementations for each test.

The Vue.js documentation advises focusing on inputs and outputs rather than implementation details. This means checking props and interactions as inputs and validating rendered elements or emitted events as outputs.

Mocking implementation details like useUsers can lead to brittle tests that break with minor changes in function names or logic. Instead of this approach, consider mocking at a higher level by replacing dependencies such as databases with mock versions that return expected data.

Improving Test Readability and Maintainability

To improve readability and maintainability of tests, focus on dependencies rather than business logic. Mocking database clients instead of specific functions can help avoid complex factory implementations or rewriting mocks for every test case.

Implementing Dependency Injection

Dependency Injection (DI) is a design pattern that helps decouple code by injecting dependencies from the outside, rather than having components create their own.

Laurent explains that while mocking the database might work, it often leads to tightly coupled code with dependencies like Supabase. This tight coupling makes it difficult to test and migrate databases because the code is deeply integrated with specific dependencies.

To solve these issues, Laurent suggests introducing an extra level of indirection (what this means is a level of abstraction). This involves wrapping dependencies that are not owned by the application in a controlled API.

Steps to Implement:

  1. Wrap Your Dependency: Create a composable function (e.g., useDatabase) that wraps the third-party dependency (e.g., useSupabaseClient).
  2. Create Control: Implement methods like findAll to handle operations internally and abstract database interactions.
  3. Focus on Business Logic: By abstracting database interactions, your composable functions can now focus purely on business logic.
  4. Simplify Testing: Instead of mocking third-party clients directly (e.g., Supabase), mock your custom composable functions.
  5. Facilitate Migration: By abstracting interactions, it becomes easier to swap databases or use different implementations for testing purposes.

The speaker points out that while this approach makes code easier to read and test by focusing on business logic rather than dependency details, it still doesn't allow for easy swapping of databases within tests without additional setup.

Laurent concludes implementing Dependency Injection by wrapping third-party dependencies into controlled APIs allows for greater flexibility in testing and migration. While this approach has its limitations regarding swapping databases without additional configurations in tests, it still provides significant benefits in decoupling logic from specific implementations.

Key Benefits of Dependency Injection

Here are the main benefits:

Separation of Concerns

  • Decoupling Business Logic: One of the primary advantages of DI is that it separates concerns by ensuring that components are not responsible for creating their own dependencies.
  • Simplified Component Responsibilities: "The first benefit obviously is the separation of concerns." This means a component like useDatabase isn't responsible for retrieving or configuring its database.

Centralization of Dependency Management

  • Centralized Configuration: DI allows for centralized creation and management of dependencies, which facilitates easier changes and maintenance.
  • Flexibility in Implementation Changes: By centralizing dependencies, it becomes simpler to switch implementations (e.g., switching databases) without altering the main business logic.

Facilitation of Implementation Changes

  • Easier Testing: With dependencies injected, it’s easier to swap out real implementations with mocks or stubs for testing purposes.
  • Support for Multiple Environments: DI allows for different implementations based on runtime configurations, making it adaptable to various environments like testing or production.

Practical Takeaways and Conclusion

At the end of his talk, Laurent highlighted how leveraging Nuxt plugins for dependency injection can help clean up architecture, decouple logic, and simplify testing without over-engineering.

Key Takeaways

The session underscored the importance of the separation of concerns in software architecture. By pulling dependencies from the IoC container, business logic remains free from dependency concerns. This approach allows for dependencies to be injected from the outside, improving testability by enabling dependency swapping at test level.

A significant benefit of this methodology is the decoupling of business logic from data retrieval tasks.

Practical Recommendations

  1. Lazy Load Imports: Ensure imports are lazy-loaded to prevent bundling unnecessary dependencies.
  2. Avoid Unnecessary Dependencies: Only provide essential services to avoid bloating the bundle entry point.
  3. Utilize Runtime Configuration: Use runtime configurations to swap dependencies per environment effectively.

If you want to learn more about dependency injection, you can watch Laurent’s talk on YouTube.

Related Courses

Start learning Vue.js for free

Eleftheria Batsou
Eleftheria Batsou
Is a passionate community manager with a coding background, keen on UX research and public speaking. She has been working in the field of tech since 2017. She likes researching and getting to know how things started or how she could improve them! She likes learning and sharing her knowledge about development/research/design and visual arts.

Comments

Latest Vue School Articles

Form and Function with Formwerk – Insights from Abdelrahman Awad’s Vue.js Nation 2025 Talk

Form and Function with Formwerk – Insights from Abdelrahman Awad’s Vue.js Nation 2025 Talk

Formwork, a flexible Vue.js library for accessible, customizable forms with seamless integration and robust validation by Abdelrahman Awad at Vue.js Nation 2025
Eleftheria Batsou
Eleftheria Batsou
The Importance and Usage of AI in Vue.js – Insights From a Live Panel at Vue.js Nation 2025

The Importance and Usage of AI in Vue.js – Insights From a Live Panel at Vue.js Nation 2025

Explore AI's impact on Vue.js development, job market insights, and future opportunities from the Vue.js Nation 2025 panel discussion.
Eleftheria Batsou
Eleftheria Batsou

Our goal is to be the number one source of Vue.js knowledge for all skill levels. We offer the knowledge of our industry leaders through awesome video courses for a ridiculously low price.

More than 200.000 users have already joined us. You are welcome too!

Follow us on Social

© All rights reserved. Made with ❤️ by BitterBrains, Inc.