Shallow render a component which is a class that uses context - javascript

I am trying to test a component using shallow with enzyme. The component I am trying to test is a class and doing context like so:
class ExampleComponent extends Component {
static contextType = AppContext;
...
and using it in that component const { url } = this.context;
When writing the test this has been used:
const contextWrapper = {
wrappingComponent: AppContext,
wrappingComponentProps: { url: 'http://appUrl.com' },
};
and here is the snapshot test as an example which is using mount as this seems to be the only way to get context to work.
it('should render correctly', () => {
const output = mount(<Comonent {...props} />, contextWrapper);
expect(shallowToJson(output)).toMatchSnapshot();
});
I have done some refactoring and in this file there is a component which is connected to the store. When using mount it expects a store to be provided to that component which is why I would like to us shallow.
If I wrap Component with a <Provider store={store}> I can get this to all work but I have a test further down trying to access state on Component but it is blank and can't find it.
Here is a snippet of the test where state is not found:
const output = mount(
<Provider store={store}>
< ExampleComponent {...newProps} />
</Provider>,
contextWrapper,
);
expect(output.state('stateValue')).toBe(true);
and this is the error shown:
ReactWrapper::state("stateValue") requires that `state` not be `null` or `undefined`
Is there a way to use shallow and context? I have found this package to 'fix' it for now shallow-with-context but I am not sure if that is correct? I have also seen old posts saying enzyme doesn't support the new context api. is that correct? What is the best method to solve this issue?

Related

How to write unit test case for componentDidMount() and export default connect(null, updateProps)(<ComponentName>) with JEST/Enzyme in React?

I have created simple react component and write test cases of components that are working correctly. I have got coverage report for the test cases.
Now, I have added react redux in my other component. this component contains componentDidMount() and export default connect(null, updateProps)(ComponentName) methods. I need to write unit test cases for these methods.
Please refer to the below code sample,
class MyComponent extends Component {
componentDidMount = () => {
//some code here
)
handleSignIn = (e) => {
//some code here
}
render() {
return (
<div>
<form onSubmit={this.handleSignIn}>
<Input
type="text"
name="inputText"
placeholder="Text"
autoFocus
required
/>
</form>
</div>
);
}
const updateProps = (dispatch) => {
return {
//some code here
};
};
export default connect(null, updateProps)(MyComponent);
In your code you have two things:
class MyComponent
and
const thisIsBasicallyAnotherComponent = connect(null, updateProps)(MyComponent);
So if you want to test your component you basically have two options. You can test your component wrapped and connected to the redux store or you can write a simple unit test for your class component as it is.
What I would recommend doing is to export your class component
- class MyComponent extends Component { // replace this
+ export class MyComponent extends Component { // with this
And then you can test your React component with Jest like any other component.
test('Link changes the class when hovered', () => {
const component = renderer.create(
<MyComponent {...mockProps} /> // !! keep in mind that you have to manually pass what you have in `updateProps` because the component is not connected to Redux store anymore
);
// ... write your test and expectations here
});
Otherwise, you can test your connected component (what is exported by default) but then you would have to wrap the component in Redux provider in order to test it.
You can find more information about testing here:
How to test components
How to test connected components
You can use Provider from react-redux or redux-mock-store to avoid need to use real reducer:
import { Provider } from 'react-redux';
import configureStore from 'redux-mock-store';
import MyComponent from './MyComponent.jsx';
const mockStore = configureStore([thunk]);
it('does something on mount', () => {
// let's mock some Redux state
const store = mockStore({ slice1: { id: 2, name: '11' } });
mount(<Provider store={store}><MyComponent /></Provider>);
expect(store.getActions()).toContainEqual({
type: 'some-type',
payload: ....
});
});
But this is that easy only to simple actions. What if you use redux-thunk and there is some loading? There are 2 ways:
Pass redux-thunk middleware to mockStore and mock network, say, by using mock-fetch or nock. Easier to set up, but also it might be overkill if you already test your Redux directly, repeating tests for "loading failed", "loading takes long" etc also to your component would mean double work.
You can mock ../yourPath/actions.js so every action there would be plain object, not a thunk. I typically go this way.
But what about "exporting unwrapped component so we could test component in isolation, without Redux"? You see, it was working when connect was the only possible API. But now with hooks like useSelector, useDispatch, useStore in mind, it's way more reliable to make tests for "my component IN Redux" first. Otherwise with "double exports" approach we may find out than converting component from class to function means way more work on patching tests, not on component itself.

What is the right way to use new React hook useContext?

I have some difficulties to understand the new way to use react Context API.
I have an app with a custom class Firebase. Now I want to make a hook to pass it. Before I used HOC (higher-order Component) and context.
My questions
Do I need to use HOC or it's a new way to do this?
Do I need the Context.Provider or it's new Hook?
Do I need to declare default value as a null or I can pass my Object
right from context.js
How can I use a new Hook instead of HOC in mine code?
Here is my code with some comments related to questions
// context.js this is my hoc
// index.jsx
import App from './App'
import Firebase, { FirebaseContext } from './components/Firebase'
const FirebaseContext = React.createContext(null)
export const withFirebase = Component => (props) => {
// I don't need to wrap it to the FirebaseContext.Consumer
// 1 But do I need this HOC or it's a new way?
const firebase = useContext(FirebaseContext)
return <Component {...props} firebase={firebase} />
}
ReactDOM.render(
// 2 Here I'm lost. Do I need the FirebaseContext.Provider or not?
// 3 Do I need to declare value her or I should do it in context.js as a default?
<FirebaseContext.Provider value={new Firebase()}>
<App />
</FirebaseContext.Provider>,
document.getElementById('root'),
)
// App.jsx
// 4 Can I use a new Hook instead of HOC here and how?
import { withFirebase } from './components/Firebase/context'
const App = () => {
const firebase = this.props.firebase // But should be useContext(FirebaseContext) or something like this?
return(...)
}
export default withFirebase(App) // I don't need this with the Hook
Any help appreciated.
You should understand it first that, useContext is just to make use of Context and acts like a consumer and not Provider.
To answer your questions
Do I need to use HOC or it's a new way to do this?
You don't need an HOC with hooks. Hooks are meant to replace HOCs and render props pattern.
Do I need the Context.Provider or it's new Hook?
There is no hooks equivalent of Context.Provider. You have to use it as is.
Do I need to declare default value as a null or I can pass my Object
right from context.js
The default value to createContext is only used if you don't pass a value props to the Context.Provider. If you pass it the default value is ignored.
How can I use a new Hook instead of HOC in mine code?
Instead of using useContext in the component returned by HOC use it directly within the component
Sample code
/ context.js this is my hoc
// index.jsx
import App from './App'
import Firebase, { FirebaseContext } from './components/Firebase'
const FirebaseContext = React.createContext(null)
ReactDOM.render(
<FirebaseContext.Provider value={new Firebase()}>
<App />
</FirebaseContext.Provider>,
document.getElementById('root'),
)
App.jsx
const App = () => {
const firebase = useContext(FirebaseContext)
return(...)
}
export default App;
Do I need to use HOC or it's a new way to do this?
No, you don't need to use HOC as best technique.
Why?
Starting from React v7.0, you can use functional-based components.
From this version efficient is to use the the latest
technique named HOOKS, which were designed to replace class and
provide another great alternative to compose behavior into your
components.
Do I need the Context.Provider or it's new Hook?
Hook like useContext() has a relation with Context.Provider.
Context is designed to share data that can be considered “global”.
The Provider component accepts a
value prop to be passed. Every Context come with a Provider.
Context.Provider component available on the context instance is used to provide the context to its child components, no matter how deep they are.
Do I need to declare default value as a null or I can pass my Object right from context.js?
No, you don't need necessarily to declare a default value.
Example of defining the context in one corner of the codebase without defaultValue.
const CountStateContext = React.createContext() // <-- define the context without defaultValue
How can I use a new Hook instead of HOC in mine code?
index.jsx
import App from './App'
import Firebase, { FirebaseContext } from './components/Firebase'
const FirebaseContext = React.createContext(null)
ReactDOM.render(
<FirebaseContext.Provider value={new Firebase()}>
<App />
</FirebaseContext.Provider>,
document.getElementById('root'),
)
Root Component: App.js, where will be used data comes form context:
const App = () => {
const firebase = useContext(FirebaseContext)
return(...)
}
export default App;

How to test a React component that has Router, Redux and two HOCs... with Jest and Enzyme?

I am currently unable to find a solution to this problem.
I have a React Component that is connected to React Router 4, Redux store and is wrapped by two HOCs. Its pretty crazy, but this is how it was coded.
Here is the export to give you an idea:
export default withFetch(Component)(fetchData, mapStateToProps)
I am trying to run some basic tests on it:
it('should render self and subcomponents', () => {
const wrapper = shallow(<Component {...props} />)
expect(toJson(wrapper)).toMatchSnapshot()
})
Which outputs a console.log/snapshot of:
<Route render={[Function: render]} />
Things tried but no succeed:
I've tried wrapping my component in the Memory Router
Supply a redux store to the component
Used .dive() and .chilndren() to try and see the children
Tried mount and render with no success.
Still keeps rendering the <Route render={[Function: render]} />
Trying out :
<MemoryRouter>
<Component {...props} />
</MemoryRouter>
Still produces the same result.
Note that I've also tried importing my component as
import { Component } from './components/'
But it returns undefined.
Any help is deeply appreciated. Thank you! 😊🙏
I assume that by <Router> you are referring to BrowserRouter.
The best way is to isolate the wrapped component and test it with testing alternatives.
For example assume that you want to test that:
// App.jsx
export const App = () =>
<Router>
<ReduxProvider>
<AppInner>
</ReduxProvider>
</Router>
My suggestion is to test AppInner with testing env of Router & ReduxProvider.
In tests:
// AppInner.test.jsx
import {mount} from 'enzyme';
import {MemoryRouter} from 'react-router';
describe('AppInner', () => {
it('should do something', () => {
const TestingComponent = () =>
<MemoryRouter>
<ReduxProvider>
<AppInner />
<ReduxProvider>
<MemoryRouter>;
const component = mount(TestingComponent);
});
})
Pay attention that I've wrapped the AppInner with MemoryRouter, it allows your mimic router but without the dependency of the browser.
For more info you can read the testing section of react-router;

Testing functional components with renderIntoDocument

I am learning to test React stateless components using the ReactTestUtils library. This is my simple component:
import React from 'react';
const Greeter = ({name,place}) => (
<h1>Hello,{name}. Welcome to the {place}.</h1>
);
export default Greeter;
This is my test spec, to get the renderIntoDocument working, I wrapped my Greeter component in a div as suggested here:
import {expect} from 'chai';
import React from 'react';
import ReactTestUtils from 'react-addons-test-utils';
import Greeter from '../Greeter';
describe('Greeter Components',() => {
it('renders correctly',() => {
var component = ReactTestUtils.renderIntoDocument(<div>
<Greeter name="Vamsi" place="Hotel California"/>
</div>);
var hasH1 = ReactTestUtils.findRenderedDOMComponentWithTag(component,'h1');
expect(hasH1).to.be.ok;
});
});
I get the error
findAllInRenderedTree(...): instance must be a composite component.
I am providing my code as jsbin here.
Since function components don't have an instance associated with them, you can't use them directly with render or renderIntoDocument. Attempting to wrap the function component is a good idea, unfortunately using a div doesn't work for a similar reason. DOM components also don't return a component instance, instead they return the underlying DOM node.
Which is all to say that you can't use the test utils function or native components as the "root" component you are rendering. Instead you will want to wrap your function components in a wrapper component that uses createClass or extends React.Component.
class Wrapper extends React.Component {
render() {
return this.props.children
}
}
let component = renderIntoDocument(<Wrapper><Greeter /></wrapper>
Gotcha's like this may be reason enough to make use of a third-party testing library like the popular enzyme, or my own take: teaspoon. Both abstract over issues like this by seamlessly wrapping and unwrapping function components for you, so you don't need to worry about what type of component you are trying to render.
Wrapping functional components in a <div> works for me. You just have to search for the component you want to test a little differently, i.e.
const props = { p1: "1" }
test('Foo renders correctly classed div', () => {
const cpt = TestUtils.renderIntoDocument(<div><Foo {...props} /></div>);
const myNode = ReactDOM.findDOMNode(cpt.childNodes[0]);
expect(myNode.className).toBe('my-class');
});
notice that you can target myNode for testing using cpt.childNodes[0]
In order to improve #monastic-panic's answer, my two cents:
You don't have to create a class for that. Do it dynamically:
import createReactClass from 'create-react-class';
// need to be a class component
const Clazz = createReactClass({
render: () => {
return <YourFunctionalComponentName {...props} />;
},
});
ReactTestUtils.renderIntoDocument(<Clazz />);

How to test a component with a nested container with React and Redux?

Due to the complexity of the application I am working on I have decided on using a nested redux container rather than passing an action as a prop down to the child components. However, this has proved to be problematic for unit testing when rendering the OuterContainer with jsdom in combination with mocha, chai and sinon.
Here is a contrived example of the view structure:
<OuterContainer>
<div>
<InnerContainer />
</div>
</OuterContainer>
where OuterContainer & InnerContainer are wrapped with connect. e.g.:
export connect(<mapStateToProps>)(<Component>)
When running tests the error I am getting is:
Invariant Violation: Could not find "store" in either the context or props of "Connect(Component)". Either wrap the root component in a `<Provider>`, or explicitly pass "store" as a prop to "Connect(Component)".
Is there a way to unwrap or stub the InnerContainer for unit testing without having to use shallow rendering?
Wrap your component in <Provider> when testing. It’s up to you whether to supply a real store or a mock with { dispatch, getState, subscribe } to it. Wrapping the outermost component in <Provider store={store}> will also make the store available to the child components at any level of nesting—just like in the app itself.
const store = createStore(reducer) // can also be a mock
ReactTestUtils.renderIntoDocument(
<Provider store={store}>
<OuterContainer />
</Provider>
)
Another approach is to export both the component to be connected and the container. The container as default, of course.
export const Comp = (props) => (<p>Whatever</p>)
export default connect(...)(Comp)
Hence, you can unit test Comp.
Not sure if this is what your problem is, but I'm sure this will probably help a few people out there looking at this feed.
I had the same error and it was a simple fix:
I had forgotten to pass my component my store object in my entry file (using webpack).
I just added an attribute to the Root component "store={store}" see below:
document.addEventListener("DOMContentLoaded", () => {
const store = configureStore();
ReactDOM.render(<Root store={store} />,
document.getElementById('content'));
});
This was my root file code for reference as well:
import React from 'react';
import { Provider } from 'react-redux';
import App from './app';
const Root = ({ store }) => (
<Provider store={ store }>
<App />
</Provider>
);
export default Root;
Hope that helps someone!
Mock the Provider component to return the child component.
Add this before describe().
jest.mock('Provider', () => ({children}) => children);

Categories