Description of the project and things I'm going to use
- use of
data-testattributes - use of
prop-typespackage to control data type of props - use of
check-prop-typesto check the errors thrown by props data type - use of
beforeEachfor wrapper, avoiding repetition - bootstrap styling
- abstractions
- create utils functions in
test/testUtils.js:findByTestAttrandcheckProps - enzyme adapter in
setupTests.js
- create utils functions in
Too many abstractions might result in hard-to-read tests ππ» less useful in diagnosing failing tests
β οΈ setup()not abstract because it's going to be different for each component
// 1. Visuals
// 2. Components needed
/ src /
|____ Congrats.js
|____ Congrats.test.jsIt renders a congratulations message when the user has guessed the word, that is, when the state blabla is set to true. Otherwise, it shouldn't display any message.
- it renders without errors
- it doesn't render text when
successprop isfalse - it renders none empty when
successprop istrue
β οΈ we don't want to test the text in case it is changed
test('renders without error', () => {
// code wil go here
});
test('renders no text when `success` prop is false', () => {
// code wil go here
});
test('renders non-empty congrats message when `succes` prop is true', () => {
// code wil go here
})-
Create a setup function that will make a shallow wrapper out of our
Congratscomponent.//... const defaultProps = { success: false }; const setup = (props={}) => { const setupProps = { ...defaultProps, ...props } // props will override defaultProps return shallow(<Congrats {...setupProps} />); }
-
Create a function that will find the node with a specified
data-testattribute valuetest/testUtils.js:
export const findByTestAttr = (wrapper, val) => { wrapper.find(`[data-test="${val}"]`) }
βΉοΈ We create this function outside of the Congrats component, in an external
testUtils.jsfile so that we can import it in any file.
-
create a generic
checkPropTypestest that can be used in any component: asks for props and the component and tests if there is any error with the props data types.import checkPropTypes from 'check-prop-types' export const checkProps = (component, conformingProps) => { const propError = checkPropTypes( component.propTypes, conformingProps, 'prop', component.name); expect(propError).toBeUndefined(); }
Congrats.test.js
test('renders without error', () => {
const wrapper = setup();
const component = findByTestAttr(wrapper, 'component-congrats');
expect(component.length).toBe(1);
})
test('renders no text when `success` prop is false', () => {
const wrapper = setup({ sucess: false });
const component = findByTestAttr(wrapper, 'component-congrats');
expect(component.text()).toBe('');
});
test('renders non-empty congrats message when `succes` prop is true', () => {
const wrapper = setup({ sucess: true });
const message = findByTestAttr(wrapper, 'congrats-message');
expect(message.text().length).not.toBe(0);
})π΄ All these tests should fail
Congrats.js
const Congrats = (props) => {
if (props.success) {
return (
<div data-test="component-congrats">
<span data-test="congrats-message">
Congratulations! You guessed the word!
</span>
</div>
);
} else {
return <div data-test="component-congrats" />
}
}π’ all tests should pass!
βAdding another test: testing the proptypes!
Congrats.test.js
//...
import checkPropTypes from 'check-prop-types'
//...
test('does not throw warning with expected props', () => {
const expectedProps = { success: false };
const propError = checkPropTypes(Congrats.propTypes, expectedProps, 'prop', Congrats.name);
expect(propError).toBeUndefined();
});ππ»
propErrorwil be undefined if the props pass all the tests.
β οΈ After creating the utility functioncheckPropsinsrc/test/testUtils.jswe will import that function instead and use it instead ofcheckPropTypes(...)
test('does not throw warning with expected props', () => {
const expectedProps = { success: false };
checkProps(Congrats, expectedProps);
});Congrats.js
Congrats.propTypes = {
success: PropTypes.bool.isRequired,
};/ src /
|___ GuessedWords.js
|___ GuessedWords.test.jsIt displays the user guesses and how many words match. If no word has been guessed, it displays instructions.
It will receive an array of objects from the parent as a prop with the shape:
[
{ guessedWord: "train", letterMatchCount: 3 },
{ guessedWord: "agile", letterMatchCount: 2 },
]
guessedWordis a string with the guessed word itself.
letterMatchCounteis an int indicating how many letters of the guessed word match the secret word.
- it renders without errors
- Proptypes test
test('does not throw warning with expected props', () => {
// code here
});
describe('if there are no words guessed', () => {
test('renders without error', () => {
// code here
});
test('renders instructions to guess a word', () => {
// code here
})
});
describe('if there are words guessed', () => {
test('renders without error', () => {
// code here
});
test('renders "guessed words" section', () => {
// code here
});
test('correct number of guessed words', () => {
//code here
});
});ππ» Case #1: No words guessed
GuessedWords.test.js
const defaultProps = [ {
guessedWords: { guessedWord: 'train', letterMatchCount: 3 }
]
};
const setup = (props={}) => {
const setupProps = { ...defaultProps, ...props };
return shallow(<GuessedWords {...setupProps } />);
}
test('does not throw warning with expected props', () => {
checkProps(GuessedWords, defaultProps);
})
describe('if there are no words guessed', () => {
let wrapper;
beforeEach(() => {
wrapper = setup({ guessedWords: [] });
})
test('renders without error', () => {
const component = findTestAttr(wrapper, 'component-guessed-words');
expect(component.length).toBe(1);
});
test('renders instructions to guess a word', () => {
const instructions = findByTestAttr(wrapper, 'guess-instructions');
expect(instructions.text().length).not.toBe(0);
})
});
π΄ all tests except for the proptypes one should fail!
GuessedWords.js
const GuessedWords = (props) => {
let contents;
if (props.guessedWords.length === 0) {
contents = <span data-test="guess-instructions">Try to guess the secret word! </span>
} else {
}
return (
<div data-test="component-guessed-words">
{ contents }
</div>
);
}π’ all tests should pass!
ππ» Case #2: There are guessed words
describe('if there are words guessed', () => {
let wrapper;
// we add more words for testing purposes
const guessedWords = [
{ guessedWord: 'train', letterMatchCount: 3 },
{ guessedWord: 'sunny', letterMatchCount: 1 },
{ guessedWord: 'return', letterMatchCount: 5 },
];
beforeEach(() => {
wrapper = setup({ guessedWords });
})
test('renders without error', () => {
const component = findByTestAttr(wrapper, 'guess-instructions');
expect(instructions.length).toBe(1);
});
test('renders "guessed words" section', () => {
const guessedWordsNode = findTestAttr(wrapper, 'guess-words');
expect(guessedWordsNode.length).toBe(1);
});
test('correct number of guessed words', () => {
const guessedWordsNodes = findTestAttr(wrapper, 'guessed-word');
expect(guessedWordsNodes.length).toBe(guessedWords.length);
});
});π΄ 'renders "guessed words" section' and 'correct number of guesses words' should fail
GuessedWords.js
const GuessedWords = (props) => {
let contents;
if (props.guessedWords.length === 0) {
contents = <span data-test="guess-instructions">Try to guess the secret word! </span>
} else {
const guessedWordsRows = props.guessedWords.map((word, index) => (
<tr data-test="guessed-word" key={index}>
<td>{word.guessedWord}</td>
<td>{word.letterMatchCount}</td>
</tr>
));
contents = (
<div data-test="guessed-words">
<h3>Guessed Words</h3>
<table>
<thead>
<tr><th>Guess</th><th>Matching Letters</th></tr>
</thead>
<tbody>
{ guessedWordsRows }
</tbody>
</table>
</div>
);
}
return (
<div data-test="component-guessed-words">
{ contents }
</div>
);
}π’ all tests should pass!
βοΈ Creating the getLetterMatchCount Helper
This function will determine how many letters a guess has in common with the secret word.
helpers/index.test.js
import { getLetterMatchCount } from './';
describe('getLetterMatchCount', () => {
const secretWord = 'party';
test('returns correct count when there are no matching letters'. () => {
const letterMatchCount = getLetterMatchCount('bones', secretWord);
expect(letterMatchCount).toBe(0);
});
test('returns correct count when there are 3 matching letters', () => {
const letterMatchCount = getLetterMatchCount('bart', secretWord);
expect(letterMatchCount).toBe(3);
});
test('returns correct count when there are duplicate letters in the guess', () => {
const letterMatchCount = getLetterMatchCount('algebra', secretWord);
expect(letterMatchCount).toBe(2);
})
})π΄ tests should fail!
helpers/index.js
export function getLetterMatchCount(guessedWord, secretWord) {
const secretLetterSet = new Set(secretWord.split(''));
const guessedLetterSet = new Set(guessedWord.split(''));
return [...secretLetterSet].filter(letter => guessedLetterSet.has(letter)).length;
}π’ all tests should pass!
jest methods:
.not
toBeUndefined()