Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ReactJs/Redux Invariant Violation: Could not find "store" in either the context or props of "Connect(LoginContainer)"

Not sure why I'm getting this error, it happened when I added connect from redux to my Login component, so I could connect my store.

FAIL src/components/auth/Login.test.js

● Test suite failed to run

Invariant Violation: Could not find "store" in either the context or props of "Connect(LoginContainer)". Either wrap the root component in a <Provider>, or explicitly pass "store" as a prop to "Connect(LoginContainer)".

enter image description here

Index.js

import React from 'react'
import ReactDOM from 'react-dom'
import { Provider } from "react-redux"
import { createCommonStore } from "./store";
import App from './App'
import css from './manage2.scss'

const store = createCommonStore();
const element = document.getElementById('manage2');
console.log("Index.js Default store", store.getState());

ReactDOM.render(
    <Provider store={store}>  // <-- store added here
        <App />
    </Provider>, element);

store.js

import React from "react"
import { applyMiddleware, combineReducers, compose, createStore} from "redux"
import thunk from "redux-thunk"
import { userReducer } from "./reducers/UserReducer"
import { authReducer } from "./reducers/AuthReducer"

export const createCommonStore = (trackStore=false) => {
    const reducers = combineReducers({
        user: userReducer,
        user: authReducer
    });

    //noinspection JSUnresolvedVariable
    const store = createStore(reducers,
        compose(
            applyMiddleware(thunk),
            window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
        )
    );

    if (trackStore) {
        store.subscribe((() => {
            console.log("  store changed", store.getState());
        }));
    }

    return store;
};

App.js

import React from 'react'
import { BrowserRouter as Router } from 'react-router-dom'
import Routes from './components/Routes'
const supportsHistory = "pushState" in window.history

export default class App extends React.Component {
    render() {
        return (
            <Router forceRefresh={!supportsHistory}>
                <Routes />
            </Router>
        );
    }
}

Routes.js

import React from 'react'
import { Route, Switch } from 'react-router-dom'
import LoginContainer from './auth/Login'
import Dashboard from './Dashboard'
import NoMatch from './NoMatch'

const Routes = () => {
    return (
        <Switch>
            <Route exact={ true } path="/" component={ LoginContainer }/>
            <Route path="/dashboard" component={ Dashboard }/>
            <Route component={ NoMatch } />
        </Switch>
    );
}

export default Routes

Finally Login.js (code removed for brevity

import React from 'react'
import { connect } from "react-redux"
import { bindActionCreators } from 'redux'; 
import { setCurrentUser } from '../../actions/authActions'
import * as api from '../../services/api'

const mapDispatchToProps = (dispatch) => {
    console.log('mapDispatchToProps', dispatch);
    return {
        setUser: (user) => {
            bindActionCreators(setCurrentUser(user), dispatch)
        }
    }
}

class LoginContainer extends React.Component {
    constructor(props) {
        super(props)

        this.state = {};

        this.handleChange = this.handleChange.bind(this);
        this.handleSubmit = this.handleSubmit.bind(this);
    }

    handleChange(e) {
    }

    handleSubmit(e) {
    }

    render() {
        return (
            <div className="app-bg">
                ...
            </div>
        )
    }
}

export default connect(null, mapDispatchToProps)(LoginContainer); 

Login.test

import React from 'react'
import ReactTestUtils from 'react-dom/test-utils'
import { mount, shallow } from 'enzyme'
import toJson from 'enzyme-to-json'
import { missingLogin } from '../../consts/errors'
import Login from './Login'
import Notification from '../common/Notification'

const loginComponent = shallow(<Login />);
const fakeEvent = { preventDefault: () => '' };

describe('<Login /> component', () => {
    it('should render', () => {
        const tree = toJson(loginComponent);
        expect(tree).toMatchSnapshot();
    });

    it('should render the Notification component if state.error is true', () => {
        loginComponent.setState({ error: true });
        expect(loginComponent.find(Notification).length).toBe(1);
    });
});

describe('User Login', () => {
    it('should fail if no credentials are provided', () => {
        expect(loginComponent.find('.form-login').length).toBe(1);
        loginComponent.find('.form-login').simulate('submit', fakeEvent);
        expect(loginComponent.find(Notification).length).toBe(1);
        const notificationComponent = shallow(<Notification message={ missingLogin }/>);
        expect(notificationComponent.text()).toEqual('Please fill out both username and password.');
    });

    it('input fields should be filled correctly', () => {
        const credentials = { username: 'leongaban', password: 'testpass' };
        expect(loginComponent.find('#input-auth-username').length).toBe(1);

        const usernameInput = loginComponent.find('#input-auth-username');
        usernameInput.value = credentials.username;
        expect(usernameInput.value).toBe('leongaban');

        const passwordInput = loginComponent.find('#input-auth-password');
        passwordInput.value = credentials.password;
        expect(passwordInput.value).toBe('testpass');
    });
});

What do you see wrong here?

like image 642
Leon Gaban Avatar asked Jun 09 '17 13:06

Leon Gaban


2 Answers

Redux recommends exporting the unconnected component for unit tests. See their docs.

In login.js:

// Named export for tests
export class LoginContainer extends React.Component {
}

// Default export
export default connect(null, mapDispatchToProps)(LoginContainer); 

And in your test:

// Import the named export, which has not gone through the connect function
import { LoginContainer as Login } from './Login';

You can then specify any props that would have come from the store directly on the component.

like image 106
James Brierley Avatar answered Oct 04 '22 21:10

James Brierley


You need to pass store as either a prop or context in your test. mount method accepts context as another parameter.

and how do you get store here? You create store the same way you created in app.js

like image 44
lavish Avatar answered Oct 04 '22 21:10

lavish