Saturday, June 18, 2016

TDD of a React-Redux connector

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? 

You might want to read a couple of previous blogs about react and redux first.

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;


Test Driven Development of a Redux Store

TLDR:

Redux is a JavaScript library that is really useful to store and manage state of a application. It has 3 important concepts:
  • The state is the current snapshot of the data.
  • A store contains the current state.
  • A reducer takes an event and produces a new snapshot of the data
Externally, Redux is based on the "Observable" pattern (the "M" of MVC) where interested objects register as being interested when the store changes state. Internally, it contains a current state that does not change and does not provide setters on the state. 

Instead, to change the state you pass an event to the redux store (like "item deleted"). The store itself then creates an entirely new state by invoking all the reducers and notifies observers.

A really useful thing from a TDD perspective is that a store can have multiple reducers. Each reducer is responsible for managing an isolated part of the state - and events are sent to all reducers. This is useful because we can test reducers independently.

The Tests

To continue the example from the React Login Page, I decided to first build a simple store for authentication data. It needed to store a boolean "loggedIn" flag and an authentication token. After a bit of thought, I decided on the following tests:
  • Default value of logged in and token flags
  • Processing a "loggedIn" event should set the flag and token
  • Processing a "loggedOut" event should set the flag and remove the token.
  • The state is immutable
(note that each of those is actually two tests).

For those who are interested, my imports are:
import { createStore } from 'redux';
import reducer, {createLoginAction, createLogoutAction} from "../../src/model/authenticationReducer.jsx";

The rationale behind those imports is:
  • I want to export the reducer by default - not a created store. 
    • This lets me combine the reducer into a bigger store elsewhere in my application. 
    • It also means that I have to create the store in my app - so that the module can't return a hacked store. It has to return a proper reducer.
    • Finally, it means I don't have to test that the store being returned does the event notification properly.
  • I felt that the methods to create the login and logout actions belonged in the same file as the reducer.
    • That way I can have private constants for the events and all logic is in the same place. 
    • It also means my tests don't need to know the internals of the events.
In addition, I used the javascript "Immutable" library as it makes copying an entire state easy.

Default Values

This test is reasonably trivial - but important. By default the user should be logged out.

it("should be logged out by default", () => {
  var store = createStore(reducer);
  expect(store.getState().get("loggedIn")).toEqual(false);
  expect(store.getState().get("token")).toBeUndefined();
});

Logging In

Again, this is a reasonably trivial test.

it("should update the logged in flag and the token on an incoming 'login' event", () => {
  var store = createStore(reducer);
  store.dispatch(createLoginAction("12345"));

  expect(store.getState().get("loggedIn")).toEqual(true);
  expect(store.getState().get("token")).toEqual("12345");
});

Logging Out

Logging out was marginally more interesting because I had to get the store into a logged in state first. While I don't like calling the login method - because it means that a broken login could break this test - it seemed the cleanest way to achieve my need.

it("should update the logged in flag and the token on an incoming 'logout' event", () => {
  var store = createStore(reducer);

  // have to log in first!
  store.dispatch(createLoginAction("12345"));

  store.dispatch(createLogoutAction());

  expect(store.getState().get("loggedIn")).toEqual(false);
  expect(store.getState().get("token")).toBeUndefined();
});

Immutability

One of the principles of Redux is that the state itself does not change - instead the reducer produces a new, modified, state. To test this, I changed the "modified" test to keep a reference to the store post-login then check the state has not changed after logout.

it("should not modify the previous state", () => {

  // have to log in first!
  store.dispatch(createLoginAction("12345"));
  var loggedInState = store.getState();

  store.dispatch(createLogoutAction());

  expect(store.getState().get("loggedIn")).toEqual(false);
  expect(store.getState().get("token")).toBeUndefined();
      
  expect(loggedInState.get("loggedIn")).toEqual(true);
  expect(loggedInState.get("token")).toEqual("12345");
});

The Code

In case you are interested, here is the code for the login reducer:

import Immutable from "immutable";

const defaultState = new Immutable.Map({
  loggedIn: false
});

const LOGIN_ACTION = "authentication.LOGGED_IN";
const LOGOUT_ACTION = "authentication.LOGGED_OUT";

export default (state = defaultState, action) => {
  switch (action.type) {
    case LOGIN_ACTION:
      return state.set("loggedIn", true).set("token", action.token);

    case LOGOUT_ACTION:
      return state.set("loggedIn", false).set("token", undefined);

    default:
      // console.log("Ignoring action: " + action.type);
      return state;
  }
};

export const createLoginAction = token => {
  return {
    type: LOGIN_ACTION,
    token: token
  };
};

export const createLogoutAction = () => {
  return {
    type: LOGOUT_ACTION
  };
};



Friday, June 17, 2016

TDD of a react component


TLDR: 

Building a react component using TDD forces you to focus on the component's interface - and ends up with a cleaner, more well defined component. However, the JavaScript space is so complex that figuring out how to write the tests is a project in itself. This blog explains bits of what you need.

The Tests

Recently at work, I've had to learn the React framework (as well as Redux and a few others). After a bit of googling, the sensible place to start was writing a simple login component. The role of this component is to show two fields: username and password, and submit the data to a loginService (I come from a back end development perspective and have developed a habit of wrapping external integration points in a service).

The tests I decided on were:

  • The component has a username field
  • The component has a password field
  • The component has a submit button
  • The component calls the login service's login method when submit is pressed
  • The component passes the value of username to the login service
  • The component passes the value of password to the login service
  • If login is successful then the component redirects to "/app"
  • If the login is not successful then the component displays an error.
These tests can, broadly, be broken down into three categories: structural, interaction start, and interaction end.

Test Setup

For those who are interested, here are my imports:
import React from 'react';
import LoginForm from '../../src/components/LoginForm.jsx';
import {mount} from 'enzyme';
import LoginService from "../../src/services/LoginService.jsx"

I use Karma/Jasmine for the test runner and, in addition, have webpack setup to produce a deployable bundle including the tests so I can run the tests in my user's web browser if they are having issues. That tells me which tests are failing in their browser with their set of plugins, configuration, etc, etc.

Structural

My initial approach to writing the structural test was:
  • Render the component using ReactTestUtils
  • Extract the DOM component using react-dom
  • Wrap the DOM in a jQuery object
  • Find the appropriate field and assert there is one of it.
However, after writing the test that way, I found out about the enzyme library. Enzyme, essentially, wraps the first 3 steps into 1 call.

For field tagging, I decided to use the "class" attribute. Could have used "id" attribute instead, but class made sense at the time.

My first test looked something like this:
it("Has a username field", () => {
  // setup
  var noop = () => {};
  var loginForm = mount(<LoginForm/>);

  // test and asserts
  expect(loginForm.find("input.username").length).toEqual(1);
});

Interaction Start

Here I quickly found that setting the value of a field in React/Enzyme is easy. Well, it's easy after an hour or so of using google. Note that I haven't written the loginService yet, so I'm just creating a mock object. Which I'd do anyway - because otherwise this test is not self-contained. Note that I eventually refactored this code to pass the LoginService object as part of the React context. That ended up being a nicer way to inject global singletons as dependancies - passing all of them pervasively through the react tree was going to be a lot of overhead for little gain.
it("Passes the username to the defined login method", () => {
  // setup
  var successPromise = { then: (s,r) => s("test token")};
  var loginService = new LoginService();
  spyOn(loginService, "login").and.returnValue(successPromise);
  var loginForm = mount(<LoginForm loginService={loginService}/>);
  var usernameField = loginForm.find("input.username");
  usernameField.simulate("change", {target: {value: "username"}});

  // test
  loginForm.find("button.submitLogin").simulate("click");

  // asserts
  expect(loginService.login).toHaveBeenCalledWith("username", jasmine.anything());
});

Interaction End

After some thought about this test, I came to the conclusion that the login form should not be responsible for the decision of what to do after the login succeeds. So I added a parameter for the "loginSuccess" function and verified that the function was called.

it("Passes the onLogin method to the 'then' promise returned by the login service", (done) => {
  // asserts
  var loginSuccess = token => {
    expect(token).toEqual("test token");
    done();
  };

  // setup
  var successPromise = { then: (s,r) => s("test token")};
  var loginService = new LoginService();
  spyOn(loginService, "login").and.returnValue(successPromise);
  loginForm = mount(
    <LoginForm onLogin={loginSuccess} loginService={loginService}/>;

  // test
  loginForm.find("button.submitLogin").simulate("click");
});