Testing Best Practices
Avoiding Implementation Details
Why We're Here
-
Tests fail when minor changes are made
-
Modules are being heavily mocked
- 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
-
Shows provided picture of the animal, alone with name and description of animal
-
Invokes a provided handler when a "share" is clicked.
- 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
deck
- 1,191