├── .gitignore ├── .prettierrc ├── README.md ├── package.json ├── public └── index.html └── src ├── __tests__ ├── isolated-and-combined.js ├── isolated-with-before-each.js ├── isolated-with-render-fn.js └── not-isolated.js ├── counter.js └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | yarn.lock 3 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 80, 3 | "tabWidth": 2, 4 | "useTabs": false, 5 | "semi": false, 6 | "singleQuote": true, 7 | "trailingComma": "all", 8 | "bracketSpacing": false, 9 | "jsxBracketSameLine": false 10 | } 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-test-isolation 2 | 3 | This is an example of how to isolate your tests and also how to combine your tests into a more cohesive test that focuses on use cases rather than functionality. 4 | 5 | I created it for [my newsletter](http://kcd.im/news). 6 | 7 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-test-isolation", 3 | "version": "1.0.0", 4 | "description": "", 5 | "keywords": [], 6 | "main": "src/index.js", 7 | "dependencies": { 8 | "@testing-library/react": "3.1.7", 9 | "@testing-library/jest-dom": "1.3.1", 10 | "react": "16.9.0", 11 | "react-dom": "16.9.0", 12 | "react-scripts": "3.0.1" 13 | }, 14 | "devDependencies": {}, 15 | "eslintConfig": { 16 | "extends": "react-app" 17 | }, 18 | "scripts": { 19 | "start": "react-scripts start", 20 | "build": "react-scripts build", 21 | "test": "react-scripts test --env=jsdom", 22 | "eject": "react-scripts eject" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 14 | 23 | React App 24 | 25 | 26 | 27 | 30 |
31 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /src/__tests__/isolated-and-combined.js: -------------------------------------------------------------------------------- 1 | import '@testing-library/jest-dom/extend-expect' 2 | import React from 'react' 3 | import {render, fireEvent} from '@testing-library/react' 4 | import {Counter} from '../counter' 5 | 6 | test('allows clicks until the maxClicks is reached, then requires a reset', () => { 7 | const {getByText} = render() 8 | const counterButton = getByText(/^count/i) 9 | 10 | // the counter is initialized to the initialCount 11 | expect(counterButton).toHaveTextContent(/3/) 12 | 13 | // when clicked, the counter increments the click 14 | fireEvent.click(counterButton) 15 | expect(counterButton).toHaveTextContent(/4/) 16 | 17 | // the counter button is disabled when it's hit the maxClicks 18 | expect(counterButton).toHaveAttribute('disabled') 19 | // the counter button no longer increments the count when clicked. 20 | fireEvent.click(counterButton) 21 | expect(counterButton).toHaveTextContent(/4/) 22 | 23 | // the reset button has been rendered and is clickable 24 | fireEvent.click(getByText(/reset/i)) 25 | 26 | // the counter is reset to the initialCount 27 | expect(counterButton).toHaveTextContent(/3/) 28 | 29 | // the counter can be clicked and increment the count again 30 | fireEvent.click(counterButton) 31 | expect(counterButton).toHaveTextContent(/4/) 32 | }) 33 | -------------------------------------------------------------------------------- /src/__tests__/isolated-with-before-each.js: -------------------------------------------------------------------------------- 1 | import '@testing-library/jest-dom/extend-expect' 2 | import React from 'react' 3 | import {render, fireEvent} from '@testing-library/react' 4 | import {Counter} from '../counter' 5 | 6 | let getByText, counterButton 7 | 8 | beforeEach(() => { 9 | const utils = render() 10 | getByText = utils.getByText 11 | counterButton = utils.getByText(/^count/i) 12 | }) 13 | 14 | test('the counter is initialized to the initialCount', () => { 15 | expect(counterButton).toHaveTextContent(/3/) 16 | }) 17 | 18 | test('when clicked, the counter increments the click', () => { 19 | fireEvent.click(counterButton) 20 | expect(counterButton).toHaveTextContent(/4/) 21 | }) 22 | 23 | test(`the counter button is disabled when it's hit the maxClicks`, () => { 24 | fireEvent.click(counterButton) 25 | expect(counterButton).toHaveAttribute('disabled') 26 | }) 27 | 28 | test(`the counter button does not increment the count when clicked when it's hit the maxClicks`, () => { 29 | fireEvent.click(counterButton) 30 | fireEvent.click(counterButton) 31 | expect(counterButton).toHaveTextContent(/4/) 32 | }) 33 | 34 | test(`the reset button has been rendered and resets the count when it's hit the maxClicks`, () => { 35 | fireEvent.click(counterButton) 36 | fireEvent.click(getByText(/reset/i)) 37 | expect(counterButton).toHaveTextContent(/3/) 38 | }) 39 | -------------------------------------------------------------------------------- /src/__tests__/isolated-with-render-fn.js: -------------------------------------------------------------------------------- 1 | import '@testing-library/jest-dom/extend-expect' 2 | import React from 'react' 3 | import {render, fireEvent} from '@testing-library/react' 4 | import {Counter} from '../counter' 5 | 6 | function renderCounter(props) { 7 | const utils = render() 8 | const counterButton = utils.getByText(/^count/i) 9 | return {...utils, counterButton} 10 | } 11 | 12 | test('the counter is initialized to the initialCount', () => { 13 | const {counterButton} = renderCounter() 14 | expect(counterButton).toHaveTextContent(/3/) 15 | }) 16 | 17 | test('when clicked, the counter increments the click', () => { 18 | const {counterButton} = renderCounter() 19 | fireEvent.click(counterButton) 20 | expect(counterButton).toHaveTextContent(/4/) 21 | }) 22 | 23 | test(`the counter button is disabled when it's hit the maxClicks`, () => { 24 | const {counterButton} = renderCounter({ 25 | maxClicks: 4, 26 | initialCount: 4, 27 | }) 28 | expect(counterButton).toHaveAttribute('disabled') 29 | }) 30 | 31 | test(`the counter button does not increment the count when clicked when it's hit the maxClicks`, () => { 32 | const {counterButton} = renderCounter({ 33 | maxClicks: 4, 34 | initialCount: 4, 35 | }) 36 | fireEvent.click(counterButton) 37 | expect(counterButton).toHaveTextContent(/4/) 38 | }) 39 | 40 | test(`the reset button has been rendered and resets the count when it's hit the maxClicks`, () => { 41 | const {getByText, counterButton} = renderCounter() 42 | fireEvent.click(counterButton) 43 | fireEvent.click(getByText(/reset/i)) 44 | expect(counterButton).toHaveTextContent(/3/) 45 | }) 46 | -------------------------------------------------------------------------------- /src/__tests__/not-isolated.js: -------------------------------------------------------------------------------- 1 | import '@testing-library/jest-dom/extend-expect' 2 | import React from 'react' 3 | // note: importing from `pure` so we don't get an automatic cleanup between tests 4 | import {render, fireEvent} from '@testing-library/react/pure' 5 | import {Counter} from '../counter' 6 | 7 | const {getByText} = render() 8 | const counterButton = getByText(/^count/i) 9 | 10 | test('the counter is initialized to the initialCount', () => { 11 | expect(counterButton).toHaveTextContent(/3/) 12 | }) 13 | 14 | test('when clicked, the counter increments the click', () => { 15 | fireEvent.click(counterButton) 16 | expect(counterButton).toHaveTextContent(/4/) 17 | }) 18 | 19 | test(`the counter button is disabled when it's hit the maxClicks`, () => { 20 | fireEvent.click(counterButton) 21 | expect(counterButton).toHaveAttribute('disabled') 22 | }) 23 | 24 | test(`the counter button does not increment the count when clicked when it's hit the maxClicks`, () => { 25 | expect(counterButton).toHaveTextContent(/4/) 26 | }) 27 | 28 | test(`the reset button has been rendered and resets the count when it's hit the maxClicks`, () => { 29 | fireEvent.click(getByText(/reset/i)) 30 | expect(counterButton).toHaveTextContent(/3/) 31 | }) 32 | -------------------------------------------------------------------------------- /src/counter.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | function Counter(props) { 4 | const initialProps = React.useRef(props).current 5 | const {initialCount = 0, maxClicks = 3} = props 6 | 7 | const [count, setCount] = React.useState(initialCount) 8 | const tooMany = count >= maxClicks 9 | 10 | const handleReset = () => setCount(initialProps.initialCount) 11 | const handleClick = () => setCount(currentCount => currentCount + 1) 12 | 13 | return ( 14 |
15 | 18 | {tooMany ? : null} 19 |
20 | ) 21 | } 22 | 23 | export {Counter} 24 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | 4 | function App() { 5 | return 'checkout the tests' 6 | } 7 | 8 | ReactDOM.render(, document.getElementById('root')) 9 | --------------------------------------------------------------------------------