TLDR
React is a framework for building reusable components. Redux is a library to store and manage changes to web application state. Wouldn't it be nice to use them together?
Design
We used the "react-redux" connector. This connector lets you wrap redux state and dispatch methods around a react component.
Test Strategy
I contemplated a few different ways to test react and redux. The two that seemed the most interesting were:
- Perform a "shallow render" of the component and check appropriate parameters were being passed to the login form component.
- Perform a full render of the component, trigger the submit button, and check that the store is appropriately modified.
While the first of those two is conceptually cleaner, I went with the second one due to context injection. Basically, by this stage I'd moved the loginService object to be passed as part of the react context instead of being a parameter - as passing the loginService as a parameter was starting to seem ugly. To inject the right context for the tests, I had to wrap the redux login form inside a context injector object. I also had to wrap the redux login form in a "provider" react-redux component. This is part of the react-redux library - and it provides the store to the components under it in the component tree.
As a side effect, the redux wrapper was not rendering as part of a shallow render. Sigh.
So here are my imports and test setup method:
import {mount} from 'enzyme';
import ContextContainer from "./ContextContainer.jsx";
import LoginService from "../../src/services/LoginService.jsx"
import React from 'react';
import ReduxLoginForm from "../../src/components/ReduxLoginForm.jsx";
import {Provider} from "react-redux";
import {createStore} from "redux";
import reducers from "../../src/model/combinedReducers.jsx";
beforeEach(() => {
loginService = new LoginService();
store = createStore(reducers);
loginForm = mount(
<Provider store={store}>
<ContextContainer loginService={loginService}>
<ReduxLoginForm />
</ContextContainer>
</Provider>
);
});
Tests
There was only one test for this component - that the form sends a dispatch method to the store if the login is successful. Note the way I'm mocking a successful promise - I found that using an actual promise object caused me issues related (I assume) to asynchronous behaviour. I'm also not happy with the way I'm invoking the form submission - it relies on knowledge about the login form, but this test is not for the login form.
it("generates a dispatch to the store on successful login", () => {
var successPromise = { then: (s,r) => s("test token")};
spyOn(loginService, "login").and.returnValue(successPromise);
expect(store.getState().authentication.get("loggedIn"))
.toEqual(false);
loginForm.find("button.submitLogin").simulate("click");
expect(store.getState().authentication.get("loggedIn"))
.toEqual(true);
expect(store.getState().authentication.get("token"))
.toEqual("test token");
});
Code
In case you are interested, here is the code for the ReduxLoginForm
import LoginForm from "./LoginForm.jsx";
import {connect} from "react-redux";
import {createLoginAction} from "../model/authenticationReducer.jsx";
var dispatchMap = dispatch => {
return {
onLogin: (username, password) => {
dispatch(createLoginAction(username, password));
}
};
};
var propsMap = () => {
var props = {
};
return props;
};
var ReduxLoginForm = connect(propsMap, dispatchMap)(LoginForm);
export default ReduxLoginForm;