Testing Best Practices

 

Avoiding Implementation Details

Why We're Here

 

  1. Tests fail when minor changes are made
     
  2. Modules are being heavily mocked
     
  3. Functions calls, constants and component state are being tested
import React from "react";
import { shallow } from "enzyme";
import MyCoolCounter from "./my-cool-counter";

describe('<MyCoolCounter />', () => {
  let wrapper;
  let mockDispatch;
  
  beforeEach(() => {
    wrapper = shallow(<MyCoolCounter />);
  });
  
  describe('when the counter is clicked', () => {
    beforeEach(() => {
      wrapper.find(BigButton).simulate('click');
    });
    
    it('dispatches INCREASE_COUNTER action', () => {
      expect(mockDispatch).toBeCalledWith({
        type: "INCREASE_COUNTER"
      });
    });
  });
});

Why Can A Test Fail

 

  • Regression: Bug in source code that should be fixed
     
  • Change Request: Tests are irrelevant to new functionality. Decide whether to update/delete them.
     
  • Refactoring: Tests have been incorrectly written to being with. This implies you never had a valid test to start with 😩😭🤕
export const add = (a, b) => a + b;

const multiply = (a, b) => {
  let total = 0;
  let repCount = 0;

  while (repCount < b) {
    total = add(total, a);
    repCount = repCount + 1;
  }

  return total;
};


import multiply, { add } from "./multiply";

jest.mock(add);

describe("multiply", () => {
  beforeEach(() => {
    jest.clearMocks();
  });
  
  it("calls add 4 times when supplied with (3,4)", () => {
    multiply(3, 4);
    expect(add).toBeCalledTimes(4);
  });
  
  it("calls add 5 times when supplied with (3,5)", () => {
    multiply(3, 5);
    expect(add).toBeCalledTimes(5);
  });
});
export const add = (a, b) => a + b;

const multiply = (a, b) => {
  return a * b;
};


import multiply from "./multiply";

describe("multiply", () => {
  it("returns 12 when supplied with 3 & 4", () => {
    expect(multiply(3, 4)).toBe(12);
  });

  it("returns 15 when supplied with 3 & 5", () => {
    expect(multiply(3, 5)).toBe(15);
  });

  it("returns 0 when supplied with 0 and 1", () => {
    expect(multiply(0, 1)).toBe(0);
  });
});

Test Validity Matrix

Having no test is better than having a false negative or false positive test

Let's Write A Test For the Following Reducer

export const initialState = {
  todos: [];
  completed: [];
}

const todoReducer = (state = initialState, action) => {
  switch(action.type) {
      case "TODO_ADDED": {
        const todo = action.payload;
        return {
          ...state,
          todos: [...state.todos, todo ],
        }
      }
      
      default:
        return state;
  }
}
import reducer, { initialState } from "./reducer";

describe(() => {
  it("returns the correct initial state", () => {
    // complete this.
  });
});

Black Box Testing Philosophy

Function, module, or class

input

output

Environment

side effect

Black Box Testing Philosophy

multiply

(2,3)

6

Environment

How Does A Component Fit the Black Box Model?

React Unit Test Example

const AnimalPreview = (props) => (
  <MuiCard>
    <MuiCardImage src={prop.imageSource} variant={1} />
    <MuiCardTitle>{prop.name}</MuiCardTitle>
    <MuiCardText>{prop.summary}</MuiCardText>
    <MuiCardActions>
      <MuiButton onClick={prop.onShareClick}>Share</MuiButton>
      <MuiButton onLearMoreClick={prop.onLearnMoreClick}>
        Learn More
      </MuiButton>
    </MuiCardActions>
  </MuiCard>
);

AnimalPreview

MuiCard

MuiCardTitle

h1

MuiCardText

p

MuiCardImage

img

MuiCardActin

button

Button

Button

button

AnimalPreview

h1

p

img

button

button

Requirements

 

  1. Shows provided picture of the animal, alone with name and description of animal
     
  2. Invokes a provided handler when a "share" is clicked.

     
  3. Invokes a provided handler when "learn more" is clicked.
import React from "react";
import {render} from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import AnimalPreview from "./animal-preview";

describe("AnimalPreview", () => {
  // write tests to meet requirements
  
  it("should render information correctly", () => {
    // setup
  
  });
  
  it("should fire provided handler when 'share' is clicked", () => {
    // setup
  });
  
  it("should fire provided handler when 'learn more' is clicked", () => {
    
  });
});
const AnimalPreview = (props) => (
  <AntTile type="square">
    <AntImg src={prop.imageSource} variant={1} />
    <AntTileTitle>{prop.name}</AntTileTitle>
    <AntTileText>{prop.summary}</AntTileText>
    <AntTileActionArea>
      <AntButton onClick={prop.onShareClick}>Share</AntButton>
      <AntButton onLearMoreClick={prop.onLearnMoreClick}>
        Learn More
      </AntButton>
    </MuiCardActions>
  </MuiCard>
);

Spot A Unit Test

 

  • Independent: Are tested in isolation. Requires nothing to be mocked. As a result, no external side effects are observed.
     
  • Programmer Perspective: Functions under test are generally internal details (e.g. utility functions).
     
  • Tested Via API: Since external side effects are minimized, these functions are mostly tested via its APIs.

deck

By Yazan Alaboudi