Description of the project and things I'm going to use
- use of
data-test
attributes - use of
prop-types
package to control data type of props - use of
check-prop-types
to check the errors thrown by props data type - use of
beforeEach
for wrapper, avoiding repetition - bootstrap styling
- abstractions
- create utils functions in
test/testUtils.js
:findByTestAttr
andcheckProps
- 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.js
It 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
success
prop isfalse
- it renders none empty when
success
prop 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
Congrats
component.//... 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-test
attribute 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.js
file so that we can import it in any file.
-
create a generic
checkPropTypes
test 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();
});
ππ»
propError
wil be undefined if the props pass all the tests.
β οΈ After creating the utility functioncheckProps
insrc/test/testUtils.js
we 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.js
It 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 },
]
guessedWord
is a string with the guessed word itself.
letterMatchCounte
is 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()