Hey. I'm not that used to testing. But I do care about dependency injection. Because I understand that decoupling is a good thing.
So, consider these two cases:
Funcional component that depends on imports:
import React from 'react';
import serviceA from './serviceA';
import serviceB from './serviceB';
function MyComponent() {
// ...
serviceA();
serviceB();
return <></>;
}
Class component that depends on imports:
import React from 'react';
import serviceA from './serviceA';
import serviceB from './serviceB';
class MyComponent extends React.Component() {
componentDidMount() {
serviceA();
serviceB();
}
// ...
render() {
return <></>;
}
}
A best approach would be to just pass all dependencies as props
. Right?
import React from 'react';
function MyComponent({serviceA, serviceB}) {
// ...
serviceA();
serviceB();
return <></>;
}
But still, I want to have defaults to my services. I mean, I want my components to resolve their own dependencies when possible. So... back to dirty imports
.
import serviceA from './serviceA';
import serviceB from './serviceB';
Apart from defaults, sometimes you just don't want certain dependencies to come from the parent component. Because they are implementation details for a given task. For example:
import React, {Component, Fragment} from 'react';
import { isSingleWord } from "../helpers/selection";
class TextEditor extends Component { ... }
I wouldn't expect isSingleWord
to be injected as a prop
. Not everything is meant to be a prop
.
However, unit testing
should test just one thing, and isSingleWord
should have it's own separated test. While TextEditor
would be tested by mocking the isSingleWord
dependency.
Test libraries like Jest
let you change the import path of your dependencies on the fly. But that's too magical for me. What could be done then?
Well, working with functions, I use to set dependencies as default parameters, like this:
import React from 'react';
import serviceA from './serviceA';
import serviceB from './serviceB';
function MyComponent({
dependencyA: serviceA,
dependencyB: serviceB,
}) {
// ...
dependencyA();
dependencyB();
return <></>;
}
That works with functional components just fine.
What about classes? I guess I could try the same trick in the constructor:
class MyComponent extends React.Component() {
constructor(props) {
super(props);
const {dependencyA, dependencyB} = props;
this.dependencyA = dependencyA;
this.dependencyB = dependencyB;
}
//...
}
But that's too verbose, and maybe not that expressive. So, I'd propose to use a HOC. Like:
import React from 'react';
import serviceA from './serviceA';
import serviceB from './serviceB';
import withDependencyInjection from './helpers/withDependencyInjection';
class MyComponent extends React.Component() {
componentDidMount() {
const {dependencyA, dependencyB} = this.props;
dependencyA();
dependencyB();
}
// ...
render() {
return <></>;
}
}
const dependencies = {
dependencyA: serviceA,
dependencyB: serviceB
};
export default withDependencyInjection(MyComponent, dependencies);
export { TestableComponent: MyComponent }; // you'd inject props yourself in test
So, with this HOC you now have defaults for your props in classes.
import React from 'react';
function withDependencyInjection(WrappedComponent, dependencies){
return function(props) {
return (
<WrappedComponent
{...props}
{...dependencies}
/>
)
}
}
export default withDependencyInjection;
Let me know your thoughts.
¿Do you import dependencies directly in your components?
¿Do you pass them as props
instead?
And ¿do you set defaults for services when they come as props
?
For testing, I guess the tendency to mock imports on the fly with Jest is really the way to go. I'm just sharing my thoughts.
Top comments (0)