-
Notifications
You must be signed in to change notification settings - Fork 46.4k
-
Notifications
You must be signed in to change notification settings - Fork 46.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Testable React Components #2559
Comments
cc:// @vjeux |
I tried for several days to use the Angular 2.0 injector but that caters extensively to "everything is a class", and React render functions naturally instantiate their own components make that approach futile. |
+1 |
And another technique wrapping react by @johnlindquist http://jsbin.com/lujona/7/edit?js,console |
I guess this really boils down for me as two problems:
|
I've been using the Angular 2.0 injector with React. Would something like this meet your requirements?https://github.com/cascadian/react-with-di/blob/master/src/index.jsx |
question: Why would use |
Great question @jquense, the trouble is that the only way to pass something to an instance form outside of it right now is by using props... It is useful to pass things in so you can pass in different implementations at test time. This also forces you to code to an interface instead of an implementation. Mocking at the module level is problematic for the reasons I listed above which is why I am hoping react will facilitate a different style. Does that answer your question? |
@cascadian that is a very cool approach indeed, that is similar to the road I traveled. I ran into the issue though injecting classes, when one a class that was injected might have a dependency of its own, I couldn't get it to work because I needed the class to pass to react create class not the instance. How does constructing a new ViewModel() receive HTTP when you construct it using . |
@cascadian does it feel odd to you using a dependency injector to inject classes instead of instances? |
gotcha! @iammerrick I do see the advantage of the simple, "pass in what I want via props, or just use the default" instead of of mocking out a module... Personally I would do something in the middle and just mock
It might be a bit overkill in this situation but it works great for making controlled/uncontrolled component props. To the larger issue, React seems pretty set (for better or worse) on their testing story, mostly due to it being the way fb does this, Jest is a good indicator that they feel that module level mocking is the right way to handle mocking in a CJS environment. I definitely see how one cold disagree with that though (I'm not a huge fan either). For me, I spent time trying to figure out a good way to do DI in React, but mostly just kept coming back to "use explicit props and don't rely on defaults" as the best, in practice. Its verbose and repetitive, but that is sort of the React paradigm, "explicitness over abstraction". |
No, but I think Angular DI's TransientScope annotation would accomplish almost the same thing if you want to avoid injecting classes.
Can you give an example? I don't quite follow. In my sample code, Angular DI injects the HTTP class into the MyComponentViewModel "factory" function, so it's in scope for the MyComponentViewModel constructor.
Then similarly, the MyComponentViewModel class is injected into the function that create's the React component. |
I don't buy the arguments listed in "Why not mock at the module level?"
That said I would like to be accommodating. So I'll try to address your concerns. First, we're moving toward encouraging shallow testing rather than deep testing. You should be asserting about renders one level deep rather than implementation details of lower level components. See: #2393 That avoids the need to mock nested components. You assert on the ReactElements rather than asserting that the classes get instantiated. Second, we're moving towards using stand-alone plain classes instead of React.createClass. So you can use any DI system that you would use on top of ES6 classes. I.e. you will be able to just wrap your classes in a DI provider. Third, your concern about getDefaultProps() {
return {
model: () => new MyComponentViewModel(new HTTP())
};
}, Then you invoke the function in getInitialState time. That makes it effectively lazy. The function can capture any additional dependencies in it's closure. |
Thank you for trying to address my concerns. I appreciate the time you put into your response. You are right about the memoization solution. I am very much looking forward to the move to ES6 classes. :-) Thank you! |
I wrote an article on dependency injection with react using properties. The TLDR of it is this:
This enables a user to override the dependencies at runtime during testing, but leave the implementation untouched. The problem is that if a user has multiple
MyComponent
s their ViewModel is shared. This is a problem using the traditionalrequire()
approach as well, where onerequire('MyComponentViewModel')
s because it is a singleton.getDefaultProps()
cacheing effectively nullifys this technique's use for instance level dependency injection.Currently in react there is only two ways to pass things in at runtime:
Because React copies props even though they are technically per instance I have no other way of passing in dependencies at runtime.
The ultimate goal of this is to write testable react components without mocking at the module level.
Why not mock at the module level?
Here is an excerpt from my article:
Some Potential Solutions
I realize that other people may not be of the same opinion about mocking at the module level but react being a library I do think it should have some notion of passing in runtime dependencies. Or somehow support a testing story that doesn't involve module level mocking.
Maybe a constructor lifecycle function that receives the props? This would enable me to per instance use a passed in prop as a dependency. Another option would be to stop caching getDefaultProps(), I imagine that is cached because props are typically viewed as immutable but there is currently no other way to pass arguments to a component.
The text was updated successfully, but these errors were encountered: