Course contentsShow
← React Native
Lesson 69 of 1,5551. The React Native ArchitectureFree lesson

Dependency Injection in Native Modules

You will be able to design modular native code that allows for easier unit testing and configuration.

In this lesson, you will learn how to design modular native code using Dependency Injection to make your React Native bridges more testable and easier to configure.

Why Hardcoding is Risky

When you build a Native Module, you might be tempted to instantiate services (like a database manager or a networking client) directly inside your class. For example, in Objective-C or Java, you might see self.apiService = [[APIService alloc] init];.

This is "tight coupling." If you want to test that module, you are forced to use the real APIService, which might try to hit a live server, slow down your tests, or behave unpredictably. Dependency Injection (DI) solves this by passing the dependency into the module from the outside instead of creating it internally.

The Dependency Injection Pattern

Instead of the module "owning" the creation of its dependencies, you provide them via the constructor or a configuration method. This allows you to swap a "Real" service for a "Mock" service during testing or configuration.

Imagine a LocationManager module in Swift:

class LocationManager {
    private let provider: LocationProvider // Protocol, not a concrete class

    // Dependency is injected here
    init(provider: LocationProvider) {
        self.provider = provider
    }
    
    func getCurrentLocation() {
        provider.fetch()
    }
}

By requiring a LocationProvider protocol rather than a specific class, you can pass a MockLocationProvider during a unit test. When running the actual app, you pass the real GPSService.

Benefits for React Native

When you design your native modules this way, you gain two massive advantages:

  1. Isolated Testing: You can verify that your module logic works correctly by injecting a "fake" dependency that returns predictable data instantly, without needing a real hardware sensor or network connection.
  2. Flexible Configuration: You can change the behavior of your native module based on the environment (e.g., using a local stub for development and a cloud service for production) without ever touching the core logic of the bridge itself.

Key Takeaway: By injecting dependencies through constructors rather than hardcoding them inside your native modules, you decouple your business logic from its implementation, enabling seamless unit testing and platform-specific configuration.