1. Library to install
     npm install --save-dev enzyme enzyme-adapter-react-16
    
  2. Setting up test environment to test React components with enzyme
     import { configure } from 'enzyme';
     import Adapter from 'enzyme-adapter-react-16';
    
     configure({ adapter: new Adapter(), disableLifecycleMethods: true });
    
  3. First red test for your component
     import React from 'react';
     import { shallow } from 'enzyme';
    
     describe('MyComponent', () => {
       it('should be rendered', () => {
         shallow(<MyComponent />);
       });
     });
    
  4. Move component rendering to setup method (e.g. beforeEach)
     describe('MyComponent', () => {
       let myComponent;
    
       beforeEach(() => {
         myComponent = shallow(<MyComponent />);
       });
     });
    
  5. If your component has controls that wouldn’t be changed after re-rendering, move their look up to setup method (e.g. beforeEach)
     describe('MyComponent', () => {
       let myComponent;
       let textInput;
       let submitButton;
    
       beforeEach(() => {
         myComponent = shallow(<MyComponent />);
         textInput = myComponent.find('input');
         submitButton = myComponent.find('button');
       });
     });
    
  6. Move event simulation to helper functions, that makes your test more descriptive
     describe('MyComponent', () => {
       // ... component setup
    
       const userEntersText = (text) => textInput.simulate('change', { targer: { value: text } });
       const userClicksSubmitButton = () => submitButton.simulate('click');
       const componentLabelText = () => myComponent.find('label').text();
    
       it('changes label text from default to user input', () => {
         userEntersText('my favourite phrase');
         userClicksSubmitButton();
    
         expect(componentLabelText()).to.be.eq('my favourite phrase');
       });
     });
    
  7. If test depends on setting up additional state of your component, move it to inner describe function
     describe('Outer', () => {
       // ... component setup
    
       const userEntersText = (text) => textInput.simulate('change', { targer: { value: text } });
       const userClicksSubmitButton = () => submitButton.simulate('click');
       const componentLabelText = () => myComponent.find('label').text();
    
       // ... tests
       describe('Inner', () => {
         beforeEach(() => {
           userEntersText('my favourite phrase');
           userClicksSubmitButton();
         });
    
         const userCleansText = () => userEntersText('');
    
         it('resets label text to default when user cleans input and submits form', () => {
           userCleansText();
           userClicksSubmitButton();
    
           expect(componentLabelText()).to.be.eq('Default label text');
         });
       });
     });
    
  8. Inject behaviour inside a component using Dependency Injection
     describe('ToDoList', () => {
       const myPredictableTaskIdGenerator = () => 1;
    
       beforeEach(() => {
         todoList = shallow(<TodoList idGenerator={myPredictableTaskIdGenerator} />);
       });
     });
    
  9. If your component has to save state to external storage, provide it using Dependency Injection
     describe('ToDoList', () => {
       let testStorage;
       beforeEach(() => {
         testStorage = mockGlobalStorage();
         todoList = shallow(<TodoList storage={testStorage} />);
       });
    
       it('saves its state to storage', () => {
         createTaskWithName('Task #1');
         createTaskWithName('Task #2');
         createTaskWithName('Task #3');
    
         const remountedTodoList = shallow(<TodoList storage={testStorage} />);
    
         expect(remountedTodoList.find('li')).to.have.length(3);
       });
     });
    
  10. Previous test can be refactored to look up child component name (e.g. Task) instead of li tag.
     import Task from 'path/to/Task/component';
    
     describe('ToDoList', () => {
       // ... setup
       it('saves its state to storage', () => {
         createTaskWithName('Task #1');
         createTaskWithName('Task #2');
         createTaskWithName('Task #3');
    
         const remountedTodoList = shallow(<TodoList storage={testStorage} />);
    
         expect(remountedTodoList.find(Task)).to.have.length(3);
       });
     });