
èªãã®ã«10å
ãã¹ãã§ã³ãŒããã«ããŒããå€ãã®åå¿ããéçºè
ãèŠãŸãããïŒ èªåã§ãã¹ãããŠããŸããïŒ ç¢ºãã«ããªãã³ã³ããŒãã³ããšã¹ã¿ãã¯ã®ç¶æ
ãäºæž¬ã§ããã®ã§ããããïŒ çãã¯éåžžã«ç°¡åã§ãããããžã§ã¯ãã®å€æŽäžã®ãšã©ãŒãåé¿ããããã§ãã
èå³ã®ããæ¹ã¯å
šå¡ãç«ã®äžã«æåŸ
ããŸãã
ãã¹ããäœæ
ãããããžã§ã¯ã
ãªããžããªã®ã¯ããŒã³ãäœæãããããããžã§ã¯ãã®ãã¹ããäœæããŠãã ããã
ããã¯éåžžã«ã·ã³ãã«ãªãããžã§ã¯ãã§ãã
ã¢ããªã±ãŒã·ã§ã³ã¯æ¬¡ã®ããã«ãªããŸãã

ãããŠã圌ã¯æ°åã®å ç®ãšæžç®ããã§ããŸããããreact-reduxãã³ãã«ã䜿çšããŠããŸãã
jestãéžæããã ãã§ãªãããªã
ãããŠããããã¯èª°ã§ããïŒ Jestã®ããã°æçš¿ã«ã¯æ¬¡ã®ããã«æžãããŠããŸãã
100ãè¶
ããäŒæ¥ãéå»6ãæéã«Jestãæ¡çšããŠããããšãéåžžã«è¬èã«æããŠããŸãã TwitterãPinterestãPaypalãnytimesãIBMïŒWatsonïŒãSpotifyãeBayãSoundCloudãIntuitãFormidableLabsãAutomatticãTrivagoãMicrosoftãªã©ã®äŒæ¥ã¯ãJavaScriptãã¹ãã®ããŒãºã«å¿ããŠå®å
šã«ãŸãã¯éšåçã«Jestã«åãæ¿ããŸããã
Jestã®ã·ã³ãã«ãã奜ããªå€§äŒæ¥ã ããã圌ãã圌ãæããŠããçç±ã§ãïŒ
- Jestã®ã€ã³ã¹ããŒã«ãšæ§æã«ã¯æå°éã®æéãå¿
èŠã§ãã
- ãã¹ãã䞊è¡ããŠå®è¡ããæ©èœã ïŒãã®äŸã§ã¯ãããã¯ããã»ã©éèŠã§ã¯ãããŸãããã倧èŠæš¡ãªã¢ããªã±ãŒã·ã§ã³ã§ã¯ãã¹ãã®é床ã倧ããªåœ¹å²ãæãããŸãïŒ
- ã¹ãããã·ã§ãããã¹ãã¯æ¬åœã«çŽ æŽãããæ©äŒã§ããããã¯ãã¹ãããã·ã§ãããäœæããããããäžéšã®ãã¹ãã®èšè¿°ãæžããããšãã§ããã³ã³ããŒãã³ãã§äœããå€æŽããããšã次åã¹ãããã·ã§ãããçæããããšãã«ãšã©ãŒã衚瀺ãããããã§ãã
- Jestã¯ã¢ãµãŒã·ã§ã³ã«Jasmineã䜿çšããŸãïŒãããã³ã°ïŒ
- ããã¯ã¹ãããã¹ãã«ãã¬ããžã衚瀺ããæ©èœ
Jestãç»å Žãããšããããã¯éåžžã«éãåäœãããããŸãããèšèšãããŠããŸããã§ãããã2016幎ã«Facebookã¯Jestãæ¹åããçŽ æŽãããä»äºãããŸããã
ãããžã§ã¯ãã®ã»ããã¢ãã
ãã¹ããå®è¡ããããã«å¿
èŠãªäŸåé¢ä¿ãèŠãŠã¿ãŸãããã
- jest-çç±ã¯æããã ãšæã
- babel-jest-çŸä»£ã®JSãå€ãJSã«å€æããŸãã
- é
µçŽ -ã³ã³ããŒãã³ãã®ç®¡çãšã¬ã³ããªã³ã°ããã䟿å©ã«ããŸãã ïŒairbnbãéçºããã©ã€ãã©ãªïŒ
- react-addons-test-utils-é
µçŽ ã®äŸåé¢ä¿ãšããŠ
- react-test-renderer-ã¹ãããã·ã§ããã®ããŒã¯ã¢ããã«ã³ã³ããŒãã³ããã¬ã³ããªã³ã°ã§ããŸã
- redux-mock-store-ã¹ãã¢ã®ã¢ãã¯ãäœæããŸã
å¿
èŠãªã®ã¯ããã ãã§ãã
ãã¹ããå®è¡ãã
ã¹ã¯ãªããã®package.jsonã«ãtestãïŒãjestããè¿œå ããŸãã ããã§ãyarn testãŸãã¯npm testã³ãã³ãã䜿çšããŠãã¹ããå®è¡ã§ããŸãã
Jestã¯__test__ãã©ã«ããŒãåæããååã.test.jsãŸãã¯.spec.jsã§ãããã¹ãŠã®ãã¡ã€ã«ãå®è¡ããŸã
ç§ãã¡å
šå¡ãæžããåŸããããèµ·ãããŸãã

- Home.spec.js-ãã®ã³ã³ããŒãã³ãã«ã¯åã³ã³ããŒãã³ãããããreduxã«æ¥ç¶ãããŠããŸãã
- calculatorActions.spec.js-ããã¯ã¢ã¯ã·ã§ã³ã®åäœãã¹ãã§ã
- calculatorReducers.spec.js-ããã¯ãreducerã®åäœãã¹ãã§ãã
次ã«ãããã€ãã®ãã¹ããäœæããŸãããã
ãã¹ãã®ç解ãšäœæ
DOMèŠçŽ ã®ååšãåçŽã«ãã§ãã¯ããå€ãã®äžèŠãªãã¹ãã±ãŒã¹ããããŸãã ããã¯æ¬åœã«å¿
èŠãªããšã§ã¯ãªãããã®ãã¹ãã«å¯ŸåŠã§ããããã«ããã®ãããªã¿ã¹ã¯ãããå Žåã¯ããã®æ¹æ³ãç¥ã£ãŠããããã«ããããæ®ããã ãã§ãã ãããããã®ãããªãã¹ããæžãããšã¯ãå§ãããŸããã
ããªããç¥ãå¿
èŠãããåã»ã¯ã·ã§ã³ã®éèŠãªãã¹ãã«ã®ã¿çŠç¹ãåœãŠãŸãã æ®ãã¯ããã¥ã¡ã³ãã§èªãããšãã§ããŸãã
1.ã³ã³ããŒãã³ã/æ¥ç¶ãããã³ã³ããŒãã³ãïŒHome.spec.jsïŒ
æ¥ç¶ãããã³ã³ããŒãã³ããšã¯ã©ãããæå³ã§ããïŒ ããã¯ãconnectãreduxãšã®éä¿¡ã«äœ¿çšããã³ã³ããŒãã³ãã§ãã Homeã³ã³ããŒãã³ãã®ã³ãŒããèŠããšãããã«2ã€ã®ãšã¯ã¹ããŒãããããŸãã
import React from 'react'; import ReactDOM from 'react-dom'; import { connect } from 'react-redux'; import { addInputs, subtractInputs, async_addInputs } from '../actions/calculatorActions'; const mapStateToProps = ({ output }) => ({ output }); export class Home extends React.Component{ render(){ ... } } export default connect(mapStateToProps, { addInputs, subtractInputs, async_addInputs })(Home);
ããããããã ã³ã³ããŒãã³ããã«ã¯æåã®ãšã¯ã¹ããŒããå¿
èŠã§ãæ¥ç¶ããã/ã¹ããŒãã³ã³ããŒãã³ãã«ã¯ãšã¯ã¹ããŒãã®ããã©ã«ããå¿
èŠã§ãã ãããŠãäž¡æ¹ã®ãªãã·ã§ã³ããã¹ãããŠãå€éšããå€ãåãåããªãã³ã³ããŒãã³ããèµ·åããŸãã
ãããŠãconnectã³ã³ããŒãã³ãã䜿çšããŠãreact-reduxããŒãããã¹ãããŸãã
ãã¹ãããã³ãŒãã«ãã³ã¬ãŒã¿ã䜿çšããªãã§ãã ããã
@connect(mapStateToProps) export default class Home extends React.Component{ ...
1.1æããªã³ã³ããŒãã³ã
æããªã³ã³ããŒãã³ãïŒæ¥ç¶ã®ãªãã³ã³ããŒãã³ãïŒãã€ã³ããŒãããŸãã
import { Home } from '../src/js/components/Home' import { shallow } from 'enzyme'
ã³ã³ããŒãã³ãã®åå¿ãªããžã§ã¯ããååŸããã ããªã®ã§ãé
µçŽ ããæµ
ãã¬ã³ããŒã䜿çšããŸãã
次ã®ãã©ã°ã¡ã³ãã詳现ã«åæããŸã
beforeEach(()=>{ wrapper = shallow(<Home output={output}/>) })
itïŒïŒé¢æ°ãå®è¡ããåã«ãbeforeEachïŒïŒã«æž¡ãããé¢æ°ãå®è¡ããããšã§ãæ¯åæŽæ°ãããã³ã³ããŒãã³ãããããããåããŠã¬ã³ããªã³ã°ããããã®ããã«åãåãããšãæå³ããŸãã
Home.jsã§ã¯ãåºåãã£ãŒã«ããthis.props.outputãæ³å®ããŠããããããã¹ãäžã«propãæž¡ãå¿
èŠãããããšã«æ³šæããŠãã ããã
<div> : <span id="output">{this.props.output}</span> </div>
1.2ã¹ããŒãã³ã³ããŒãã³ã
ããã«èå³æ·±ãããšã«ãã¹ããŒãã³ã³ããŒãã³ãããã¹ãã«ã€ã³ããŒãããŸãã ã€ã³ããŒãã¯æ¬¡ã®ããã«ãªããŸãã
import ConnectedHome, { Home } from '../src/js/components/Home'
ãŸããredux-mock-storeã䜿çšããŸãã
import configureStore from 'redux-mock-store'
次ã«ãã¹ããŒãã³ã³ããŒãã³ãããã¹ãããããã®2ã€ã®ãªãã·ã§ã³ãæ€èšããŸãã ããªããäžçªå¥œããªãã®ãéžæããå¿
èŠããããŸãã
ãã®ãã¹ãã§ã¯ãã³ã³ããŒãã³ããmapStateToPropsãä»ããŠåãåãinitialStateãäžèŽãããã©ããã確èªããŸãã
ã³ãŒããèŠããšãæåã®ãã¹ããšåãããšãè¡ããããã«ä»ã®æ¯èŒãããã€ãè¿œå ããŸããããæåã®ãã¹ãã§å®è£
ã§ããŸãã
æåãš2çªç®ã®ãªãã·ã§ã³ã§ã¯ãã¢ãã¯ã¹ãã¢ã䜿çšãããããå€æŽãã³ãããã§ããŸããããè¿œå ã®ã©ã€ãã©ãªãªãã§å®éã®ã¹ãã¢ã䜿çšã§ããŸãã
ãã ããããã¯åäœãã¹ãã®äžéšã§ã¯ãªãããããå§ãããŸããã
1.3ã¹ãããã·ã§ãã
Jestã§ãã1ã€æ°ã«å
¥ã£ãŠããã®ã¯ãã¹ãããã·ã§ãããã¹ãã§ãã
jestãåããŠã¹ãããã·ã§ãããåé€ãããšãã«æ¯èŒããå Žåããã¹ããã¡ã€ã«ã®é£ã®__snapshots__ãã©ã«ããŒã«ã¹ãããã·ã§ãããé
眮ããŸãã ã¹ãããã·ã§ãããäœæããã«ã¯ãæåã«ã³ã³ããŒãã³ããã¬ã³ããªã³ã°ããå¿
èŠããããŸãããã®ããã«ãreact-test-rendererã©ã€ãã©ãªãã€ã³ããŒãããŸãã
import renderer from 'react-test-renderer'
Home.jsã³ã³ããŒãã³ãã®ã¹ãããã·ã§ããã¯æ¬¡ã®ããã«ãªããŸã
exports[`>>>HOME --- Snapshot +++capturing Snapshot of Home 1`] = ` <div className="container"> <h2> using React and Redux </h2> <div> Input 1: <input placeholder="Input 1" type="text" /> </div> <div> Input 2 : <input placeholder="Input 2" type="text" /> </div> <div> Output : <input placeholder="Output" readOnly={true} type="text" value={10} /> </div> <div> <button id="add" onClick={[Function]}> Add </button> <button id="subtract" onClick={[Function]}> Subtract </button> </div> <hr /> </div> `;
ãŸããHome.jsãã¡ã€ã«ã®å
容ãå€æŽããŠãã¹ããå®è¡ããããšãããšããšã©ãŒãçºçããŸãã

ã¹ãããã·ã§ãããæŽæ°ããã«ã¯ã-uãã©ã°ãæå®ããŠãã¹ããå®è¡ããå¿
èŠããããŸã
jest test -u || yarn test -u
ããã«ãããã¹ãããã·ã§ãããäžèŽããªãå Žåãã¹ãããã·ã§ãããæ¯èŒãããšãã«ãšã©ãŒãçºçããããããã¹ãã«å€ãã®æéãè²»ããå¿
èŠããããŸããã ã¹ãããã·ã§ããã«ã¯ã³ã³ããŒãã³ãã®å°éå
·ãšç¶æ
ãå«ãŸããŠããŸãããã³ã³ããŒãã³ãã§ãããããã¹ãããå¿
èŠãããå Žåã¯ã2ã€ã®ã€ã³ã¹ã¿ã³ã¹ãäœæããå¿
èŠããããŸãã
ã³ã³ããŒãã³ãã®ã¹ãããã·ã§ããã ãã§ãªããã¬ãã¥ãŒãµãŒã®ã¹ãããã·ã§ãããäœæã§ããŸããããã¯éåžžã«äŸ¿å©ã§ãã
ããšãã°ããã®ãããªãã¹ããäœæããŸãã
import reducer from './recipe'; describe('With snapshots ', () => { it('+++ reducer with shapshot', () => { expect(calculatorReducers(undefined, { type: 'default' })).toMatchSnapshot(); }); it('+++ reducer with shapshot', () => { const action = { type: 'ADD_INPUTS', output: 50, }; expect(calculatorReducers(undefined, action)).toMatchSnapshot(); }); });
次ã®ãã®ãåŸãããŸãã

ããã§ãæåã«ããã«ã€ããŠèšåããçç±ãããããŸããã
2. ActionCreatorsïŒcalculatorActions.spec.jsïŒ
ActionCreatorsãè¿ããã®ãšæ¬æ¥ããã¹ããã®ãæ¯èŒããã ãã§ãã
import { addInputs,subtractInputs } from '../src/js/actions/calculatorActions' describe('>>>ACTION --- Test calculatorActions', ()=>{ it('+++ actionCreator addInputs', () => { const add = addInputs(50) expect(add).toEqual({ type:"ADD_INPUTS", output:50 }) }); it('+++ actionCreator subtractInputs', () => { const subtract = subtractInputs(-50) expect(subtract).toEqual({ type:"SUBTRACT_INPUTS", output:-50 }) }); });
3.ã¬ãã¥ãŒãµãŒïŒcalculatorReducers.spec.jsïŒ
actionCreatorsãšåããããç°¡åã«ãã¬ãã¥ãŒãµãŒããã¹ãããŸãã
import calculatorReducers from '../src/js/reducers/calculatorReducers' describe('>>>REDUCER --- Test calculatorReducers',()=>{ it('+++ reducer for ADD_INPUT', () => { let state = {output:100} state = calculatorReducers(state,{type:"ADD_INPUTS",output:500}) expect(state).toEqual({output:500}) }); it('+++ reducer for SUBTRACT_INPUT', () => { let state = {output:100} state = calculatorReducers(state,{type:"SUBTRACT_INPUTS",output:50}) expect(state).toEqual({output:50}) }); });
éåæã¢ã¯ã·ã§ã³
æãéèŠãªããšã®1ã€ã¯ãéåæã¢ã¯ã·ã§ã³ãŸãã¯å¯äœçšã®ããã¢ã¯ã·ã§ã³ããã¹ãããããšã§ãã
éåæã¢ã¯ã·ã§ã³ã®å Žåãredux-thunkã䜿çšããŸãã éåæã¢ã¯ã·ã§ã³ãã©ã®ããã«èŠãããèŠãŠã¿ãŸãããã
export const async_addInputs = output => dispatch => new Promise((res, rej) => { setTimeout(() => res(output), 3000); }).then(res => dispatch(addInputs(res)));
ãããŠãä»åºŠã¯ãã®ããã®ãã¹ããäœæããŸãã å¿
èŠãªãã®ããã¹ãŠã€ã³ããŒãããå¿
èŠããããŸãã
import { addInputs, subtractInputs, async_addInputs } from '../src/js/actions/calculatorActions'; import configureMockStore from 'redux-mock-store'; import thunk from 'redux-thunk'; const mockStore = configureMockStore([ thunk ]);
次ã«ããã¹ããäœæããŸãã
describe('>>>Async action --- Test calculatorActions', () => { it('+++ thunk async_addInputs', async () => { const store = mockStore({ output: 0 }); await store.dispatch(async_addInputs(50)); expect(store.getActions()[0]).toEqual({ type: 'ADD_INPUTS', output: 50 }); }); });
async_addInputsé¢æ°ãèŠãŠãã¢ã¯ã·ã§ã³ãçµäºããã®ãåŸ
ã£ãŠããã¹ãããå¿
èŠãããå¿çãè¿ããŸãïŒãã®å Žåã¯æåã§ãïŒã ãããã£ãŠãåŒã³åºãããã¢ã¯ã·ã§ã³ã¯1ã€ã ãã§ãããADD_INPUTSã§ãããšèšããŸãã
å
ã«é²ã¿ãŸãããããã¹ãã§ã¯ãããªããã£ããã£ãã·ã¥ã·ã¹ãã ã確èªã§ããŸãã
ïŒãããžã§ã¯ãããã§ã¯ãªãåãªãäŸïŒ
it('does check if we already fetched that id and only calls fetch if necessary', () => { const store = mockStore({id: 1234, isFetching: false }}); window.fetch = jest.fn().mockImplementation(() => Promise.resolve()); store.dispatch(fetchData(1234));
äžèšãèŠããšãããããã«ãID 1234ã¯æ¢ã«ã¹ãã¢ã«ããããã®IDã®ãªã¯ãšã¹ãã§ããŒã¿ãåä¿¡ããå¿
èŠã¯ãããŸããã
ããã§ã¯ãéåæã¢ã¯ã·ã§ã³ã®æãåºæ¬çãªãã¹ãã調ã¹ãŸããã ãŸããã¢ããªã±ãŒã·ã§ã³ã«ã¯ä»ã®å¯äœçšããããããããŸããã ããšãã°ãfirebaseã®ããã«ããŒã¿ããŒã¹ãçŽæ¥æäœããããä»ã®APIãçŽæ¥æäœãããããŸãã
ã³ãŒãã«ãã¬ããž
ãã¹ãã«ãã¬ããžã¬ããŒãã¯ãããã«äœ¿çšã§ããŸãã çµ±èšã衚瀺ããã«ã¯ã-coverageãã©ã°ãæå®ããŠãã¹ããå®è¡ããå¿
èŠããããŸã
yarn test -- --coverage || npm test -- --coverage
次ã®ããã«ãªããŸãã

ãããžã§ã¯ãã§ãã©ã«ãã確èªãããšãã¬ããŒãããã©ãŠã¶ã«è¡šç€ºãããindex.htmlãã¡ã€ã«ã®ããã«ãã¬ããžãã©ã«ããèŠã€ããããšãã§ããŸãã

ãã¡ã€ã«ãã¯ãªãã¯ããŠããã¹ãã«ãã¬ããžã®è©³çŽ°ãªçµ±èšã確èªããŸãã
ãããjestã§ã®ãã¹ãã«ããããã¹ãŠã®éèŠãªãã€ã³ãã§ãã ããããã¹ã¿ãŒããåŸããã¹ãã®ããã«èŠéãåºããããã«ãããã¥ã¡ã³ãã§ä»ã®ãã¹ãŠãèŠãããšãã§ããŸãã