Tests Strategies for React and Redux

When the Firefox Add-ons team ported addons. mozilla. org to a single page application backed by a good API , we chose React and Redux for effective state management , delightful developer tools , and testability. Achieving the testability component isn’ t completely obvious classes competing tools and techniques.

Below are some testing techniques that are working really well for us.

Testing must be fast plus effective

We want the tests to be lightning fast to ensure that we can ship high-quality features rapidly and without discouragement. Waiting for tests could be discouraging, yet tests are crucial just for preventing regressions, especially while restructuring an application to support new features.

Our strategy is to just test what’ s necessary in support of test it once. To achieve this we check each unit in isolation, faking away its dependencies. This is a technique referred to as unit examining and in our situation, the unit is typically a single React component .

Unfortunately, it’ s very hard to do this safely in a dynamic vocabulary such as JavaScript since there is no fast method to make sure the fake objects are in synchronize with real ones. To solve this particular, we rely on the safety associated with static typing (via Flow ) in order to alert us if one element is using another incorrectly — some thing an unit test might not capture.

A suite associated with unit tests combined with static kind analysis is very fast and efficient. We use Jest because it as well is fast, and because it allows us to focus on a subset of checks when needed.

Testing Redux connected components

The risks of testing in isolation in just a dynamic language are not entirely relieved by static types, especially given that third-party libraries often do not deliver with type definitions (creating all of them from scratch is cumbersome). Also, Redux-connected components are hard to isolate simply because they depend on Redux functionality to keep their particular properties in sync with condition. We settled on a strategy exactly where we trigger all state adjustments with a real Redux store . Redux is vital to how our application operates in the real world so this makes our own tests very effective.

Because it turns out, testing with a real Redux store is fast. The design associated with Redux lends itself very well in order to testing due to how actions, reducers, and state are decoupled from one another . The tests give the right opinions as we make changes to app state. This also makes it feel like a great fit for testing. Aside from screening, the Redux architecture is great for debugging, scaling, and especially development .

Think about this connected component as an example: (For brevity, the examples in this article do not establish Flow types but you can learn about the right way to do that right here . )

 import  connect  from 'react-redux';
import  compose  from 'redux';

// Define a functional React component.
foreign trade function UserProfileBase(props) 
  return (
    <span>props.user.name</span>
  );


// Establish a function to map Redux state to properties.
function mapStateToProps(state, ownProps) 
  return  user: state.users [ownProps.userId]  ;


// Export the last UserProfile component composed of
// a situation mapper function.
export default compose(
connect(mapStateToProps),
)(UserProfileBase);
 
 

You may be tempted to test this simply by passing in a synthesized user home but that would bypass Redux and all sorts of your condition mapping logic. Rather, we test by dispatching a genuine action to load the user into condition and make assertions about what the particular connected component rendered.

 import  mount  through 'enzyme';
import UserProfile from 'src/UserProfile';

describe('< UserProfile> ', () => 
  it('renders a name', () => 
    const store = createNormalReduxStore();
    // Simulate fetching an user from an API and loading it into state.
    store.dispatch(actions.loadUser( userId: 1, name: 'Kumar' ));

    // Render with an user ID so it can retrieve the user from state.
    const root = mount(<UserProfile userId=1 store=store />);

    expect(root.find('span')).toEqual('Kumar');
  );
 ); 
 

Rendering the full component with Enzyme’ s mount() ensures mapStateToProps() is working and that the reducer did what this specific component anticipated. It simulates what would happen when the real application requested an user through the API and dispatched the result. Nevertheless , since mount() renders all components which includes nested components, it doesn’ big t allow us to test UserProfile in remoteness. For that we need a different approach making use of shallow making , explained below.

Shallow rendering for dependency shot

Let’ s state the UserProfile component depends on a UserAvatar aspect of display the user’ s picture. It might look like this:

  export function UserProfileBase(props) 
  const  user  = props;
  return (
    <div>
      <UserAvatar url=user.avatarURL />
      <span>user.name</span>
    </div>
  );

 
 

Considering that UserAvatar will have unit tests of its personal, the UserProfile test just has to ensure it calls the interface associated with UserAvatar correctly. What is its interface? The particular interface to any React component is just its qualities . Flow helps to validate residence data types but we in addition need tests to check the data values.

With Enzyme, we don’ t have to substitute dependencies with reproductions in a traditional dependency injection feeling. We can simply infer their lifestyle through superficial rendering . A test would appear something like this:

 import UserProfile,  UserProfileBase  through 'src/UserProfile';
import UserAvatar from 'src/UserAvatar';
import  shallowUntilTarget  from '. /helpers';

describe('< UserProfile> ', () => 
  it('renders an UserAvatar', () => 
    const user = 
      userId: 1, avatarURL: 'https://cdn/image.png',
    ;
    store.dispatch(actions.loadUser(user));

    const root = shallowUntilTarget(
      <UserProfile userId=1 store=store />,
      UserProfileBase
    );

    expect(root.find(UserAvatar).prop('url'))
      .toEqual(user.avatarURL);
  );
 );
 
 

Instead of calling mount() , this test renders the particular component using a custom helper known as shallowUntilTarget() . You might already be familiar with Enzyme’ s shallow() but that just renders the first component in a shrub. We needed to create a helper known as shallowUntilTarget() that will render all “ wrapper” (or increased order ) components till reaching our target, UserProfileBase .

Ideally Enzyme will ship a feature just like shallowUntilTarget() soon, but the implementation is simple. This calls root. dive() in a loop till root. is(TargetComponent) returns true.

With this shallow rendering approach, it is currently possible to test UserProfile in isolation though dispatch Redux actions like a genuine application.

The test searches for the UserAvatar component in the tree and just makes sure UserAvatar will receive the correct qualities (the render() function of UserAvatar is certainly not executed). If the properties of UserAvatar modify and we forget to update the test, test might still pass, but Movement will alert us about the breach.

The elegance associated with both React and shallow making just gave us dependency shot for free, without having to inject any dependencies! The key to this testing strategy would be that the implementation of UserAvatar is free to develop on its own in a way that won’ t crack the UserProfile tests. If changing the particular implementation of an unit forces you to definitely fix a bunch of unrelated tests, it’ s a sign that your testing technique may need rethinking.

Crafting with children, not properties

The power of React plus shallow rendering really come into concentrate when you create components using kids instead of passing JSX via attributes. For example , let’ s say a person wanted to wrap UserAvatar in a common InfoCard regarding layout purposes. Here’ s the way to compose them together as kids:

  foreign trade function UserProfileBase(props) 
  const  user  = props;
  return (
    <div>
      <InfoCard>
        <UserAvatar url=user.avatarURL />
      </InfoCard>
      <span>user.name</span>
    </div>
  );

 
 

After making this change, exactly the same assertion from above will still function! Here it is again:

 expect(root. find(UserAvatar). prop('url'))
. toEqual(user. avatarURL);
 
 

In some cases, you may be tempted to pass JSX through properties instead of through kids. However , common Enzyme selectors such as root. find(UserAvatar) would no longer work. Let’ s look at an example of passing UserAvatar in order to InfoCard through a content property:

  export function UserProfileBase(props) 
  const  user  = props;
  const avatar = <UserAvatar url=user.avatarURL />;
  return (
    <div>
      <InfoCard content=avatar />
      <span>user.name</span>
    </div>
  );

 
 

This is nevertheless a valid implementation but it’ h not as easy to test.

Testing JSX passed through properties

Sometimes you really can’ big t avoid passing JSX through qualities. Let’ s imagine that InfoCard needs complete control over rendering some header content material.

  foreign trade function UserProfileBase(props) 
  const  user  = props;
  return (
    <div>
      <InfoCard header=<Localized>Avatar</Localized>>
        <UserAvatar url=user.avatarURL />
      </InfoCard>
      <span>user.name</span>
    </div>
  );

 
 

How would you test this particular? You might be tempted to do a full Chemical mount() as opposed to the shallow() render. You might think it will provide you with much better test coverage but that extra coverage is not necessary — the particular InfoCard component will already have tests from the own. The UserProfile test just has to make sure InfoCard gets the right properties. Here’ s how to test that.

 import  shallow  from 'enzyme';
import InfoCard through 'src/InfoCard';
import Localized from 'src/Localized';
import  shallowUntilTarget  from '. /helpers';

describe('< UserProfile> ', () => 
  it('renders an InfoCard with a custom header', () => 
    const user = 
      userId: 1, avatarURL: 'https://cdn/image.png',
    ;
    store.dispatch(actions.loadUser(user));

    const root = shallowUntilTarget(
      <UserProfile userId=1 store=store />,
      UserProfileBase
    );

    const infoCard = root.find(InfoCard);

    // Simulate how InfoCard will render the
    // header property we passed to it.
    const header = shallow(
      <div>infoCard.prop('header')</div>
    );

    // Now you can make assertions about the content:
    expect(header.find(Localized).text()).toEqual('Avatar');
  );
 );
 
 

This is better than a full mount() because it enables the InfoCard implementation to evolve openly so long as its properties don’ to change.

Testing element callbacks

Aside from transferring JSX through properties, it’ t also common to pass callbacks in order to React components. Callback properties allow it to be very easy to build abstractions around typical functionality. Let’ s imagine we have been using a FormOverlay component to render an modify form in a UserProfileManager component.

 import FormOverlay through 'src/FormOverlay';

export class UserProfileManagerBase expands React. Component 
  onSubmit = () => 
    // Pretend that the inputs are controlled form elements and
    // their values have already been connected to this.state.
    this.props.dispatch(actions.updateUser(this.state));
  

  render() 
    return (
      <FormOverlay onSubmit=this.onSubmit>
        <input id="nameInput" name="name" />
      </FormOverlay>
    );
  


// Foreign trade the final UserProfileManager component.
export arrears compose(
// Use connect() through react-redux to get props. dispatch()
connect(),
)(UserProfileManagerBase);
 
 

How can you test the integration of UserProfileManager along with FormOverlay ? You could be tempted once again to do a full mount() , especially if you’ re examining integration with a third-party component, something similar to Autosuggest . However , a full mount() is not necessary.

Just like in previous illustrations, the UserProfileManager test can simply check the attributes passed to FormOverlay . This is safe because FormOverlay may have tests of its own and Stream will validate the properties. The following is an example of testing the onSubmit property.

 import FormOverlay from 'src/FormOverlay';
import  shallowUntilTarget  through '. /helpers';

describe('< UserProfileManager> ', () => 
  it('updates user information', () => 
    const store = createNormalReduxStore();
    // Create a spy of the dispatch() method for test assertions.
    const dispatchSpy = sinon.spy(store, 'dispatch');

    const root = shallowUntilTarget(
      <UserProfileManager store=store />,
      UserProfileManagerBase
    );

    // Simulate typing text into the name input.
    const name = 'Faye';
    const changeEvent = 
      target:  name: 'name', value: name ,
    ;
    root.find('#nameInput').simulate('change', changeEvent);

    const formOverlay = root.find(FormOverlay);

    // Simulate how FormOverlay will invoke the onSubmit property.
    const onSubmit = formOverlay.prop('onSubmit');
    onSubmit();

    // Make sure onSubmit dispatched the correct ation.
    const expectedAction = actions.updateUser( name );
    sinon.assertCalledWith(dispatchSpy, expectedAction);
  );
 );
 
 

This tests the particular integration of UserProfileManager and FormOverlay without counting on the implementation of FormOverlay . It uses sinon in order to spy on the shop. dispatch() method to guarantee the correct action is dispatched once the user invokes onSubmit() .

Every modify starts with a Redux action

The Redux architecture is straightforward: when you want to change application state, give an action. In the last example of examining the onSubmit() callback, the test simply true a dispatch of actions. updateUser(... ) . That’ s it. This test presumes that once the updateUser() action is sent, everything will fall into place.

So how would an application such as ours actually update the user? We might connect a saga to the activity type. The updateUser() saga would be accountable for making a request to the API and dispatching further actions when receiving a reaction. The saga itself will have device tests of its own. Since the UserProfileManager check runs without any sagas, we don’ t have to worry about mocking out the particular saga functionality. This architecture can make testing very easy; something like redux-thunk might offer similar benefits.

Summary

These good examples illustrate patterns that work really well in addons. mozilla. org for resolving common testing problems. Here is a summarize of the concepts:

  • We dispatch real Redux activities to test application state changes.
  • We test each element only once using shallow rendering.
  • We resist full DEM rendering (with mount() ) as much as possible.
  • We test component integration simply by checking properties.
  • Stationary typing helps validate our element properties.
  • We imitate user events and make statements about what action was dispatched.

Want to get more involved with Firefox Add-ons community? There are a web host of ways to contribute to the addons ecosystem – plus plenty to learn, whatever your abilities and level of experience.

Kumar hacks on Mozilla web solutions and tools for various tasks, such as those supporting Firefox Add-ons . He or she hacks on lots of random open source tasks too.

More content by kumar303…

If you liked Tests Strategies for React and Redux by Then you'll love Web Design Agency Miami

Add a Comment

Your email address will not be published. Required fields are marked *

Shares