Dependency Injection with React and TypeScript
By depending on an interface instead of an implementation we can make our components more flexible and more testable by injecting different dependencies when testing. Let’s start!
We first create a service with an interface. Then we implement this service interface using the React Native’s Share module.
// services/share-service.ts
import { Share as ReactNativeShare } from "react-native";
export interface ShareService {
share: (message: string) => void;
}
export const ReactNativeShareService: ShareService = {
share: (message) => ReactNativeShare.share({ message }),
};
Now we can use this service. We make shareService optional with a default value of ReactNativeShareService. This way we can inject another dependency in tests.
// components/share-button.ts
import React, { FunctionComponent } from "react";
import { Button } from "react-native";
import {
ShareService,
ReactNativeShareService,
} from "../services/share-service";
interface ShareButtonProps {
title: string;
shareMessage: string;
shareService?: ShareService;
}
const ShareButton: FunctionComponent<ShareButtonProps> = (props) => {
const { title, shareMessage, shareService = ReactNativeShareService } = props;
return (
<Button title={title} onPress={() => shareService.share(shareMessage)} />
);
};
Testing is very easy now. Let’s write a test for ShareButton.
// components/share-button.test.tsx
import React from "react";
import { ShareService } from "../services/share-service";
import { render, fireEvent } from "react-native-testing-library";
import ShareButton from "./share-button";
it("triggers share on press", () => {
const mockShareService: ShareService = {
share: jest.fn(),
};
const { getByText } = render(
<ShareButton
title={"Share"}
shareMessage={"A message"}
shareService={mockShareService}
/>
);
const button = getByText("Share");
fireEvent.press(button);
expect(mockShareService.share).toBeCalledWith("A message");
});
Bonus
Now it’s also easier to replace Share from react-native with another library. We’re importing Share only in one file. All we have to do is make sure we can implement the same interface using the new dependency.