ãã¹ãŠã®JSéçºè ã¯é ããæ©ãããã¹ããæžãå§ãã圌ãããã以åã«ãããªãã£ãããšãåŸæããŠããŸãã éå»æ°å¹Žã«ããã£ãŠãReactãAngularããŸãã¯Vueãªã©ã«åºã¥ããã³ã³ããŒãã³ãéçºã«èª°ããåŸã ã«åãæ¿ããŠä»¥æ¥ãã³ã³ããŒãã³ãã¯éåžžå°ããããã¹ããã¯ããã«ç°¡åã§ããããããã¹ãã®æ®åã«å¥ã®æšé²åãäžããŸããã ãã®èšäºã§ã¯ãReactã§ã®ã³ã³ããŒãã³ãã®ãã¹ãã«ã€ããŠèª¬æããŸãã
ãã®èšäºã§è±èªã®çšèªã䜿çšããå¿ èŠãããããšãäºåã«è¬çœªããŸãã ãã¹ãã®åéã§æšæºãšãªã£ãŠããããã€ãã®ãã¬ãŒãºã翻蚳ãããšãç解ã倱ãããè¿œå æ å ±ã®æ€çŽ¢ãè€éã«ãªããŸãã
JSãããžã§ã¯ãã§ãã¹ããæŽçããããã«å¿ èŠãªãŠãŒãã£ãªãã£ãèŠãŠã¿ãŸãããã
- ãã¹ãã©ã³ããŒã¯ããã¹ãã§ãã¡ã€ã«ãååŸããŠèµ·åãããã¹ãçµæã衚瀺ãããŠãŒãã£ãªãã£ã§ãã ãã®åéã§æã人æ°ã®ãããŠãŒãã£ãªãã£ã¯ãã¢ã«ãšã«ã«ãã§ãã
- ã¢ãµãŒã·ã§ã³ã©ã€ãã©ãª-ãã¹ãã®æ¡ä»¶ããã§ãã¯ããããã®äžé£ã®æ©èœãåããã©ã€ãã©ãªã ChaiãšExpectã¯ããã®åéã§æã䜿çšãããŠããã©ã€ãã©ãªã§ãã
- ã¢ãã¯ã©ã€ãã©ãª-ãã¹ãäžã«ã¢ãã¯ãäœæããããã«äœ¿çšãããã©ã€ãã©ãªã ãã®ãŠãŒãã£ãªãã£ã䜿çšãããšããã¹ã察象ã®ã³ã³ããŒãã³ãã®æ¥ç¶éšåããç®çã®åäœãã·ãã¥ã¬ãŒããããã¹ã¿ããã«çœ®ãæããããšãã§ããŸãã ããã§æã人æ°ã®ããéžæè¢ã¯ã·ãã³ã§ãã
Reactã®æ¢åã®ãã¹ãããŒã«ãæ€èšãããããã®ããŒã«ã䜿çšããç°¡åãªãã¹ãã®äŸã瀺ããŸãã ãããžã§ã¯ãã¢ã»ã³ããªã®æ§ææ¹æ³ãES6ã®ãã©ã³ã¹ããŒããªã©ã«ã€ããŠã¯èª¬æããŸãããHabréã§å¿ èŠãªèšäºãæ¢ããããèŠã€ãããããå Žåã¯ããã®ãã¹ãŠãèªåã§èª¿ã¹ãããšãã§ããŸãã æåŸã®æ段ãšããŠ-æžããŠãç§ã¯ããªããå©ããããšããŸãã
ãŸãããã®èšäºã®æ çµã¿ã®äžã§ããã¹ãã®åéã§äžè¬çãªæãäžè¬çãªã©ã€ãã©ãªã®ããã©ã€ã³ãã䜿çšã«ã€ããŠã¯æ€èšããŸããããæè¿ç»å Žããã©ã€ãã©ãªãèŠãŠããããã泚ç®ã«å€ãããã©ãããç解ããããšããŸãã
第1å¹
æåã«å¿ èŠãªã®ã¯ãå°æ¥ã®ãã¹ãçšã®TestRunnerã§ãã çŽæã©ããããã®ã¬ãã¥ãŒã§ã¯KarmaãMochaãªã©ã®äžè¬çãªãŠãŒãã£ãªãã£ã¯å¯Ÿè±¡å€ã§ãã æ°ããããŒã«ãFacebook Jestãæ€èšããŠãã ããã Mochaãšã¯ç°ãªããJestã¯ã»ããã¢ããããããžã§ã¯ããžã®çµ±åãéåžžã«ç°¡åã§ãåæã«éåžžã«æ©èœçã§ãã ããã¯1幎åã¯ããªããæ²ããããããžã§ã¯ãã§ãããããã¹ãã«å¿ èŠãªæ©èœã®å€ããæ¬ ããŠããŸãããããšãã°ãå¯å€æ©èœãã¡ã€ã«ã远跡ããéåææ©èœãç£èŠã¢ãŒãã®ãã¹ãã¯ãããŸããã§ããã çŸåšããã®è£œåã¯ãã§ã«ããªããé åçãã§ãããã¢ã«ãã«ã«ããªã©ã®ã¢ã³ã¹ã¿ãŒãšç«¶åã§ããŸãã ããã«ãã¡ã³ããã¯æ°å¹Žåã«ã¯å®å šã«æ¬ ããŠããæ¬ é¥ãè¿ éã«ä¿®æ£ãå§ããŸããã ããã§ã¯ãJestã§ã§ããããšãèŠãŠã¿ãŸãããã
- é©ãã»ã©ç°¡åã«ãããžã§ã¯ãã«çµ±åã§ããŸãã
Jestã«ã¯å¿ èŠãªãã®ããã¹ãŠå«ãŸããŠãããããæ°ååã®å°ããªã©ã€ãã©ãªãé 眮ããŠçžäºã®å¯Ÿè©±ãæ§æããå¿ èŠã¯ãããŸããã 人æ°ã®ããKarma + Mocha + Sinonæããã¹ãã«å°ãªããšãäžåºŠäœ¿çšãã人ã¯ç解ã§ããŸãããæ®ãã¯ç§ã®èšèãåãå ¥ããªããã°ãªããŸããã
- ãã¹ãã®å®è¡ãšãã¹ãçµæã®è¡šç€º
Jestã«ã¯ãæ€çŽ¢ãæ§æããŠãã¹ããå®è¡ããã®ã«ååãªãã©ã¡ãŒã¿ãŒãå«ãŸããŠããããããããžã§ã¯ãããã³ã¿ã¹ã¯çšã«ãã€ã§ãæ§æã§ããŸãã
- assertã©ã€ãã©ãªãå«ãŸããŠããŸãããä»ã®ã©ã€ãã©ãªã«çœ®ãæããããšãã§ããŸã
Jestã¯Jasmineã©ã€ãã©ãªã®2çªç®ã®ããŒãžã§ã³ã«åºã¥ããŠãããããäžåºŠäœ¿çšããããšãããã°ãæ§æã¯ããªãã¿ã®ããã«æããŸãã ãžã£ã¹ãã³ãæ°ã«å ¥ããªãå Žåã¯ããæ°ã«å ¥ãã®ã©ã€ãã©ãªã䜿çšã§ããŸãã
- åãã¹ããåå¥ã®ããã»ã¹ã§å®è¡ã§ããããããã¹ãã®å®è¡ãé«éåã§ããŸã
- éåæã³ãŒããæäœããã¿ã€ããŒã䜿çšããŠã³ãŒãããã¹ãã§ãã
- ã€ã³ããŒããããã³ã³ããŒãã³ãã®ãã¹ã¿ãããèªåçã«äœæã§ãã
å®éãããã¯Jestã®éèŠãªæ©èœã®1ã€ã§ãããæ§æããã®ã¯ããªãå°é£ã§ãã ãã®ãããå€ãã®äººãäžåºŠã«Jestã®äœ¿çšãæåŠããæ°ããããŒãžã§ã³ã§ã¯ããã©ã«ãã§ç¡å¹ã«ãªã£ãŠããŸãã - ã€ã³ã¿ã©ã¯ãã£ããªãŠã©ããã¢ãŒãã§åäœå¯èœ
Jestã«ã¯éåžžã«ã¯ãŒã«ãªã€ã³ã¿ã©ã¯ãã£ãã¢ãŒãããããå€æŽãããã³ã³ããŒãã³ãã®ãã¹ãã ãã§ãªããããšãã°ãgitã®æåŸã®ã³ããããæåŸã®ã倱æããã¹ãããŸãã¯ããã¿ãŒã³ãã䜿çšããŠååã§æ€çŽ¢ããããšãã§ããŸãã - ãã¹ãã§ãããžã§ã¯ãã®ã«ãã¬ããžãåéã§ããïŒã«ãã¬ããžïŒ
- jsdomãå«ãŸããŠããããããã©ãŠã¶ãªãã§ãã¹ããå®è¡ã§ããŸã
- ã¹ãããã·ã§ããã䜿çšããŠã³ã³ããŒãã³ãããã¹ãã§ããŸã
Jestãã§ããããšã¯ããã ãã§ã¯ãããŸããã ãã®ãŠãŒãã£ãªãã£ã®è©³çŽ°ã«ã€ããŠã¯ãå ¬åŒWebãµã€ãfacebook.github.io/jestãã芧ãã ããã ãã ããJestã«ã¯æ¬¡ã®ãããªæ¬ ç¹ããããŸãã
- Jestã®ããã¥ã¡ã³ãã¯ããªããç¡é§ã®ãªãããã®ã§ãããå€ãã®å Žåãèªåã§çããæ¢ããgithubãŸãã¯ã¹ã¿ãã¯ãªãŒããŒãããŒã«ã€ããŠèª¿ã¹ãªããã°ãªããŸããã
- ãã©ãŠã¶ã§ãã¹ããå®è¡ã§ããŸãã
ã¯ãããã©ã¹ã¯ãã€ãã¹ã§ããããŸãã 確ãã«ãç§ã«ãšã£ãŠããã¯é倧ãªãã€ãã¹ã§ã¯ãããŸããã ãã©ãŠã¶ã«ãã£ãŠè¡šç€ºãç°ãªãå¯èœæ§ãããç¶æ³ãåé¿ããããšããŠããŸãã
- ãã¹ãå®è¡ããã£ããéå§ããŸãã
ã¹ããŒã¹ã¿ãŒãã«ã€ããŠã JestããŒã ã¯ããã¹ãã®å®è¡ãé«éåããããã«çµ¶ããæ¹åãè¡ã£ãŠããŸãã ããããªãŒã»ã¢ãã©ã¢ãã圌ãã«å ãã£ãåŸãç¶æ³ã¯å€§ããæ¹åããŸãããããã¯ç¢ºèªãããŠããŸãã ããã«ãããããããç§ã®å人çãªææ ã«ãããšãã«ã«ã+ã¢ã«ã䜿çšããŠäœæãããã¹ãã¯ãJestã䜿çšããŠäœæãããã¹ããããæ©ãéå§ãããåäœããŸããã æéãçµã€ã«ã€ããŠãã¿ããªããã®æ¬ ç¹ãåãé€ãããšãé¡ã£ãŠããŸãã
ããã§ã¯ãããã€ãã®ãã¹ããæžããŠãå®éã«ã©ã®ããã«èŠãããèŠãŠã¿ãŸãããã éå§ããã«ã¯ã2ã€ã®æ°å€ã®åèšãèšç®ããåçŽãªé¢æ°ã䜿çšããŸãã
function sum(a, b) { return a + b; }
ãã®é¢æ°ã®ãã¹ãã¯æ¬¡ã®ããã«ãªããŸãã
describe('function tests', () => { it('should return 3 for arguments 1 and 2', () => { expect(sum(1, 2)).toBe(3); }); });
ãã¹ãŠãã·ã³ãã«ã§ããªãã¿ã§ãã å¥ã®é¢æ°åŒã³åºããè¿œå ããŠãé¢æ°ãè€éã«ããŸãããã
function initial() { return 1; } function sum(a, b) { return initial() + a + b; }
ã¢ãããã¯èŠçŽ ã«å¯ŸããŠé©åã«æ§ç¯ããããã¹ãã§ã¯ãæ®ãã®ã³ãŒããžã®äŸåé¢ä¿ããã¹ãŠé€å€ããå¿ èŠããããŸãã ãããã£ãŠãåæé¢æ°ã®èª€ã£ãæäœã®å¯èœæ§ãåèšé¢æ°ã®ãã¹ãããé€å€ããå¿ èŠããããŸãã ãããè¡ãã«ã¯ãå¿ èŠãªå€ãè¿ãåæé¢æ°ã®ãã¹ã¿ãããäœæããŸãã ååŸãããã¹ãã¯æ¬¡ã®ãšããã§ãã
describe('function tests', () => { it('should return 4 for arguments 1 and 2', () => { initial = jest.fn((cb) => 1); expect(sum(1, 2)).toBe(4); }); });
ããã§é¢æ°ãè€éã«ããŠããŸããsumé¢æ°ãéåæã«ãã次ã«ãç®çã®çµæãè¿ãåã«èããå¿ èŠããããšä»®å®ããŸãã
function initial(salt) { return 1; } function sum(a, b) { return new Promise((resolve, reject) => { setTimeout(function(){ resolve({ value: initial(1) + a + b, param1: a, param2: b, }); }, 100); }); }
å€æŽãèæ ®ã«å ¥ããããã«ããã¹ããçµäºããŸãã
describe('function tests', () => { beforeAll(() => { jest.useFakeTimers(); }); afterAll(() => { jest.useRealTimers(); }); it('should return 4 for arguments 1 and 2', () => { initial = jest.fn((cb) => 1); const result = sum(1, 2); result.then((result) => { expect(result).not.toEqual({ value: 3, param1: 1, param2: 2, }); expect(initial).toHaveBeCalledWith(1); }); jest.runTimersToTime(100); return result; }) });
ãã®ãã¹ãã§ã¯ãããã€ãã®æ°ããJestæ©èœãé©çšããŸããã
- ãŸããæéã管çã§ããããã«ãåœã®ã¿ã€ããŒã䜿çšããããã«åœŒã«äŸé ŒããŸãã
- 第äºã«ãéåæé¢æ°ããã¹ãããæ¹æ³ãèŠãŸãã
- 第äžã«ããã¹ãæ€èšŒã§ãã¬ãšãªããžã§ã¯ãæ¯èŒã䜿çšããæ¹æ³ãèŠãŸãã
- 4çªç®ã«ãã¢ãã¯é¢æ°ãå¿ èŠãªãã©ã¡ãŒã¿ãŒã§åŒã³åºãããããšããã¹ãããæ¹æ³ãèŠãŸãã
Reactã³ã³ããŒãã³ãããã¹ãããæ¹æ³ãèŠãŠã¿ãŸãããã ãŠãŒã¶ãŒã«æšæ¶ã衚瀺ãã衚瀺ãããããã¹ãã®ã¯ãªãã¯ããã£ããããåçŽãªã³ã³ããŒãã³ãããããšããŸãã
export default class Wellcome extends React.Component { onClick() { this.props.someFunction(this.props.username); } render() { return ( <div> <span onClick={this.onClick.bind(this)}>Wellcome {this.props.username}</span> </div> ); } }
ãã¹ãããŠã¿ãŸãããïŒ
import React from 'react'; import TestUtils from 'react-addons-test-utils'; import Wellcome from './welcome.jsx'; describe('<Welcome />', () => { it('Renders wellcome message to user', () => { const onClickSpy = jest.fn(); const username = 'Alice'; const component = ReactTestUtils.renderIntoDocument( <Wellcome username= {username} someFunction={onClickSpy} /> ); const span = TestUtils.findRenderedDOMComponentWithTag( component, 'span' ); TestUtils.Simulate.click(span); expect(span.textContent).toBe('Wellcome Alice'); expect(onClickSpy).toBeCalledWith(username); }); });
ã芧ã®ãšãããããã§ã¯è€éãªããšã¯äœããããŸããã React Test Utilsã䜿çšããŠã³ã³ããŒãã³ããã¬ã³ããªã³ã°ããDomããŒããæ€çŽ¢ããŸãã ãã以å€ã®å Žåããã¹ãã¯éåžžââã®Jestãã¹ããšå€ãããŸããã
ããã§ãJestã䜿çšããŠãã¹ããäœæããã³å®è¡ããæ¹æ³ãæ€èšããŸããããå ã«é²ãåã«ãJestã®ãã1ã€ã®æ©èœã§ããã¹ãããã·ã§ãããã¹ãã«ã€ããŠå°ã説æããŸãããã ã¹ãããã·ã§ãããã¹ãã¯ãReactããªãŒã¹ãããã·ã§ãããJSONãªããžã§ã¯ããšããŠä¿åãããã以éã®ãã¹ãå®è¡ã§çµæã®æ§é ãšæ¯èŒããæ©èœã§ãã
倧ãŸãã«èšã£ãŠããã®ãããªå°è±¡ã圢æããããã®ãã¹ããåããŠå®è¡ãããšãã¯ãæã§ãã®åŠ¥åœæ§ã確èªããã³ãŒããªããžããªã«ã³ãããããŸãã ãããŠ-åºæ¥äº-ãã¹ãã®ãã®åŸã®ãã¹ãŠã®å®è¡ã¯ããªããžããªããã®å°è±¡ãäœãèµ·ãã£ãããšæ¯èŒããŸãã
ãã®æ©èœã¯æè¿ãJestã«ç»å ŽããŸããããå人çã«ã¯ãç§ã®é©æ°ãè€éãªææ ãåŒãèµ·ãããŸããã äžæ¹ã§ã¯ã䜿çšããã®ã䟿å©ã§ããããšãããããŸãã-äžéšã®ãã¹ãã¯å®éã«ç°¡åã«ãªããŸããïŒå¯Ÿè©±æ§ããªããå®éã«æ§é ã確èªããã ãã§ããå ŽåïŒããã¹ãã§ã³ãŒããè€è£œããå¿ èŠã¯ãããŸããã äžæ¹ããã€ãã¹ãèŠãŸããïŒç§ã«ãšã£ãŠã®ãã¹ãã¯ã³ãŒãã®ããã¥ã¡ã³ãã§ãããã¹ãããã·ã§ããã«åºã¥ãããã¹ãã¯æ¬è³ªçã«ã解æŸãããæ©äŒãäžããã¢ãµãŒããèæ ®ããããšãªãã2ã€ã®ã³ã³ããŒãã³ãããªãŒãæ¯èŒããã ãã§ãã ããã«ããã®ã¢ãããŒãã§ã¯ãæåã«ã³ã³ããŒãã³ããã¹ããäœæãã次ã«ã³ãŒãèªäœãäœæãããšãã«ãå€å žçãªTDDã®å¯èœæ§ã奪ããŸãã ãããããã®æ©èœã¯ééããªããã¡ã³ãèŠã€ãããšæããŸãã
ã³ã³ããŒãã³ãã§ã©ã®ããã«æ©èœããããèŠãŠã¿ãŸãããã
import React from 'react'; import renderer from 'react-test-renderer'; import Wellcome from './welcome.jsx'; describe('<Welcome />', () => { it('Renders wellcome message to user', () => { const onClickSpy = jest.fn(); const username = 'Alice'; const component = renderer.create( <Wellcome username={username} someFunction={onClickSpy} /> ); const json = component.toJSON(); expect(json).toMatchSnapshot(); expect(onClickSpy).toHaveBeCalledWith(username); }); });
å ·äœçã«ã¯ããã¹ãã¯å€§å¹ ã«ç°¡çŽ åãããŠããŸããïŒæ¢ã«ç°¡åã§ããïŒã æ§é ããã倧ããªã³ã³ããŒãã³ãã®å Žåããã¹ããååã«æžãããå€§å¹ ã«ç°¡çŽ åã§ããŸãã ãã¹ããå®è¡ããŠãäœãèµ·ãããèŠãŠã¿ãŸãããã
ããã§ãjestã¯ç§ãã¡ã®ããã«åãäœããŸããã ãã£ã¹ãã®å 容ã¯æ¬¡ã®ãšããã§ãã
exports[<Welcome /> Renders wellcome message to user 1] = ` <div> <span onClick={[Function]}> Wellcome Alice </span> </div> `;
ãã®åã¯ãã³ã³ããŒãã³ãã®htmlæ§é ãè¡šãããç®ã§ãæ€èšŒããã®ã«äŸ¿å©ã§ãã
äžèšã®æ¬ ç¹ã«å ããŠããã£ã¹ãã«åºã¥ããã¹ãã®å¥ã®æ¬ ç¹ã«ééããŸããã HOCã³ã³ããŒãã³ãïŒredux-formãªã©ïŒã䜿çšããå Žåããã²ããã«ã¯ãã¹ã察象ã®ã³ã³ããŒãã³ãã¯å«ãŸããŸããããredux-formã®ã©ãããŒãå«ãŸããŸãã ãããã£ãŠãã³ã³ããŒãã³ãèªäœããã¹ãããã«ã¯ãããããšã¯ã¹ããŒããããšãšãã«ãredux-formãå¿ èŠãšããã³ã³ãã©ã¯ããã·ãã¥ã¬ãŒãããå¿ èŠããããŸãã
ååãšããŠãHOCã³ã³ããŒãã³ãã1ã€ããã°å€§äžå€«ã§ãã ããããããšãã°ãå Žåã«ãã£ãŠã¯3ã€ãreact-reduxãã1ã€ãredux-fromãã2ã€ãreact-intlãã3ã€ãæã€ããšãã§ããŸãã åŸè ã®å Žåãã³ã³ããŒãã³ããããã©ã°ã€ã³ãããããšãã§ããªããããã³ãŒãããã¹ãããããšã¯éåžžã«å°é£ã§ããã³ã³ããŒãã³ãã«æ£çŽãªããŒã«ã©ã€ãºAPIãæ·»ä»ããå¿ èŠããããŸãã ãããã©ã®ããã«è¡ãããã- ããã§èŠãããšãã§ããŸã ã
ãŸãšãããšã ããã§ãã³ã³ããŒãã³ãã§ãã¹ããå®è¡ããããã«å¿ èŠãªãã®ããã¹ãŠæããŸããã ãããããã¹ããåçŽåããŠæ¹åããæ¹æ³ãããäžåºŠèŠãŠã¿ãŸãããã
第2å¹
Reactã³ã³ããŒãã³ãã®ãã¹ãã«ã€ããŠæåã«èãããããè¡ãæ¹æ³ã«é¢ããæ å ±ãæ¢ãå§ãããšããã¹ããŠãŒãã£ãªãã£React Test Utilitesã®ããã±ãŒãžã«ééããå¯èœæ§ããããŸãã ãã®ããã±ãŒãžã¯FacebookããŒã ã«ãã£ãŠéçºããããã®ã§ãã³ã³ããŒãã³ããã¹ããäœæã§ããŸãã ãã®ããã±ãŒãžã«ã¯ã次ã®æ©èœããããŸãã
- DOMã§ã®ã³ã³ããŒãã³ãã¬ã³ããªã³ã°
- DOMèŠçŽ ã®ã€ãã³ãã·ãã¥ã¬ãŒã·ã§ã³
- ãã¢ããã³ã°ãã³ã³ããŒãã³ã
- DOMã§ã¢ã€ãã ãæ€çŽ¢ãã
- æµ ãã³ã³ããŒãã³ãã®ã¬ã³ããªã³ã°
ã芧ã®ãšãããäžé£ã®æ©èœã¯éåžžã«å¹ åºããã³ã³ããŒãã³ãã®ãã¹ããäœæããã®ã«ååã§ãã React Test Utilitesã䜿çšããŠãã¹ããã©ã®ããã«èŠãããã®äŸã¯ãåã®ã»ã¯ã·ã§ã³ã§åæããŸããã
import React from 'react'; import TestUtils from 'react-addons-test-utils' import Wellcome from './welcome.jsx'; describe('<Welcome />', () => { it('Renders wellcome message to user', () => { const onClickSpy = jest.fn(); const username = 'Alice'; const component = ReactTestUtils.renderIntoDocument( <Wellcome username= {username} someFunction={onClickSpy} /> ); const span = TestUtils.findRenderedDOMComponentWithTag( component, 'span' ); TestUtils.Simulate.click(span); expect(span.textContent).toBe('Wellcome Alice'); expect(onClickSpy).toBeCalledWith(username); }); });
ããããããã€ãã®çç±ã«ããããæšæºçãªæ¹æ³ãã§ã¯ãªãããã¹ãã«React Test Utilitesã䜿çšããŸããã ãŸãããã®ã©ã€ãã©ãªã«ã¯éåžžã«è²§åŒ±ãªããã¥ã¡ã³ãããããããã«å¯ŸåŠããããã«ãåå¿è ã¯ã€ã³ã¿ãŒãããäžã®åçã®æ€çŽ¢ãç©æ¥µçã«äœ¿çšããå¿ èŠããããŸãã 第äºã«ãç§ãã¡ã«ãšã£ãŠæãããããããæ©èœã§ããã³ã³ããŒãã³ãã®æµ ãã¬ã³ããªã³ã°ã¯ãé·ãéå®éšæ®µéã«ãããã©ã®ãããªåœ¢ã§ãå®çŸããŠããŸããã 代ããã«ãArbnbããŒã ã«ãã£ãŠéçºãããReactã¢ããªã±ãŒã·ã§ã³ã®ãã¹ãã§ãã§ã«éåžžã«äººæ°ã®ããçŽ æŽãããEnzymeã©ã€ãã©ãªã䜿çšããŸãã åºæ¬çã«ãEnzymeã¯ãä»ã®3ã€ã®ã©ã€ãã©ãªã®ã¢ããªã³ã§ããã©ã€ãã©ãªã§ãïŒReact TestUtilsãJSDOMãããã³CheerIOïŒ
- TestUtils-Reactã³ã³ããŒãã³ãããã¹ãããããã«Facebookã«ãã£ãŠäœæãããã©ã€ãã©ãª
- JSDOMã¯ãDOMã®JSå®è£ ã§ãã ãã©ãŠã¶ããšãã¥ã¬ãŒãã§ããŸã
- CheerIO-DOMèŠçŽ ãæäœããããã®Jqueryã®é¡äŒŒç©
ãã¹ãŠãçµã¿åãããŠå°ãè¿œå ããããšã«ãããEnzymeã䜿çšãããšãReactã³ã³ããŒãã³ãã®ãã¹ããç°¡åãã€æ確ã«äœæã§ããŸãããŸããTest Utilitesã®æ©èœã«å ããŠã次ã®ããšãå¯èœã§ãã
- ã³ã³ããŒãã³ããã¬ã³ããªã³ã°ããããã®3ã€ã®ãªãã·ã§ã³ïŒæµ ããããŠã³ããããã³ã¬ã³ããªã³ã°
- JQueryã®ãããªã³ã³ããŒãã³ãæ€çŽ¢æ§æ
- ã³ã³ããŒãã³ãåã䜿çšããŠã³ã³ããŒãã³ããæ€çŽ¢ããŸãïŒDisplayNameãã©ã¡ãŒã¿ãŒã§ãã®ååãæå®ããå Žåã®ã¿ïŒ
- ãã©ã¡ãŒã¿ãŒå€ïŒpropsïŒã䜿çšããŠã³ã³ããŒãã³ããæ€çŽ¢ããŸã
ã¯ããEnzymeã«ã¯TestRunnerãå«ãŸããŠããããã³ã³ããŒãã³ãã®ãã¹ã¿ããã®äœææ¹æ³ãããããŸããããããã«ã¯ãã§ã«JestããããŸãã
ã³ã³ããŒãã³ããã¬ã³ããªã³ã°ããããã®3ã€ã®ãªãã·ã§ã³ãšããããæäŸãããã®ã詳ããèŠãŠã¿ãŸãããã ãããã£ãŠãEnzymeã«ã¯ãã³ã³ããŒãã³ããã¬ã³ããªã³ã°ããJqueryã¹ã¿ã€ã«ã®äžé£ã®ã¡ãœããã§åæ§ã®ã©ãããŒãè¿ã3ã€ã®ã¡ãœããããããŸãã
- æµ ãã¬ã³ããªã³ã°-é µçŽ ã¯ã³ã³ããŒãã³ãèªäœã®ã¿ãã¬ã³ããªã³ã°ããããã«åã蟌ãŸããã³ã³ããŒãã³ãã®ã¬ã³ããªã³ã°ãç¡èŠããŸã
- å®å šãªDomã¬ã³ããªã³ã°-ã³ã³ããŒãã³ããšãã®ãã¹ãŠã®åã蟌ã¿ã³ã³ããŒãã³ãã®å®å šãªã¬ã³ããªã³ã°
- éçã¬ã³ããªã³ã°-æž¡ãããã³ã³ããŒãã³ãã®éçHTMLãã¬ã³ããªã³ã°ããŸã
EnzymeãæäŸãããã¹ãŠã®ã¡ãœããã®ãªã¹ããæäŸããããã§ã¯ãããŸããã
- ã³ã³ããŒãã³ããŸãã¯DOMèŠçŽ ãèŠã€ãã
- ã³ã³ããŒãã³ãã®ã³ã³ãã³ããJSXããŒã¯ã¢ãããšæ¯èŒãã
- ã³ã³ããŒãã³ãã®ããããã£ã確èªãã
- ã³ã³ããŒãã³ãã®ç¶æ ãæŽæ°ãã
- ã€ãã³ãããšãã¥ã¬ãŒããã
ãããã¯é µçŽ ã®ãã¹ãŠã®æ©èœã§ã¯ãããŸããã ã©ã€ãã©ãªã®ããã¥ã¡ã³ãã§å®å šãªãªã¹ããèŠã€ããããšãã§ããŸãã3çš®é¡ã®ã¬ã³ããªã³ã°ã®éãã«çŠç¹ãåœãŠãŸãã
æµ ãã¬ã³ããªã³ã°ãå¯èœã«ãããã®ãšãã®é åã¯äœã§ããïŒ ãŸããçµã¿èŸŒã¿ã³ã³ããŒãã³ãã«ã€ããŠèããã®ã§ã¯ãªããã³ã³ããŒãã³ãèªäœã®ã¿ããã¹ããããšãã«éäžããæ©äŒãäžããŠãããŸãã çµã¿èŸŒã¿ã³ã³ããŒãã³ãã«ãã£ãŠçæãããæ§é ãã©ã®ããã«å€åãããã¯çµ¶å¯Ÿã«æ°ã«ããŸãããããã¯ãã¹ããäžæãããã®ã§ã¯ãããŸããã ãããã£ãŠãä»ã®ã³ã³ããŒãã³ãããåé¢ããŠã³ã³ããŒãã³ãããã¹ãã§ããŸãã ããããããã¯ãã¹ããããã³ã³ããŒãã³ãããŸã£ãããã¹ãããªããšããæå³ã§ã¯ãããŸããã ãããããã¹ããããã³ã³ããŒãã³ãã«ããããã£ãæ£ããæž¡ãããšããã¹ãã§ç¢ºèªã§ããŸãã ããã«ããã®ãããªãã¹ãã®ãã1ã€ã®å©ç¹ã¯ãDOMãå¿ èŠãšããªãããããã®ãããªãã¹ãã®é床ãFull Domã¬ã³ããªã³ã°ã䜿çšããå Žåãããã¯ããã«éãããšã§ãã ããããæ®å¿µãªãããåžžã«è¡šé¢ã¬ã³ããªã³ã°ã®ã¿ã䜿çšã§ããããã§ã¯ãããŸããã ããšãã°ã䜿çšããWellcomeã³ã³ããŒãã³ãã«å ããŠã次ã®ã³ã³ãã³ããæã€Homeã³ã³ããŒãã³ããçšæããŸãã
import React, { PropTypes, Component } from 'react' import Wellcome from './Wellcome' class Home extends Component { onChangeUsername(e) { this.props.changeUsername(e.target.value); } render() { return ( <section className='home'> <h1>Home</h1> <Wellcome username={this.props.username} /> <input type="text" name="username" value={this.props.username} onChange={this.onChangeUsername.bind(this)} /> </section> ) } } Home.propTypes = { changeUsername: PropTypes.func.isRequired } export default Home
ãã®ã³ã³ããŒãã³ãã®ãã¹ããäœæããŸãããã
import React from 'react' import { shallow } from 'enzyme' import Home from './Home' import Wellcome from './Wellcome'; describe('<Home />', () => { it('should render self and Wellcome', () => { const renderedComponent = shallow( <Home username={'Alice'} changeUsername={jest.fn()} /> ); // console.log(renderedComponent.debug()); expect(renderedComponent.find('section').hasClass('home')).toBe(true); expect(renderedComponent.find('h1').text()).toBe('Home'); expect(renderedComponent.find('input').length).toBe(1); expect(renderedComponent.find(Wellcome).props().username).toBeDefined(); expect(renderedComponent.contains(<Wellcome username={'Alice'} />)).toBe(true); }); it('should call changeUsername on input changes', () => { const changeUsernameSpy = jest.fn(); const renderedComponent = shallow( <Home username={'Alice'} changeUsername={changeUsernameSpy} ); renderedComponent.find('input').simulate('change', { target: { value: 'Test' } }); expect(changeUsernameSpy).toBeCalledWith('Test'); }); });
Enzymeã§æµ ãã¬ã³ããªã³ã°ãã©ã®ããã«æ©èœãããã確èªããããã«ããããã°æ©èœã䜿çšããŠã次ã®ã³ãŒãã衚瀺ãããã®ã確èªããŸãã
<section className="home"> <h1> Home </h1> <Wellcome username="Alice" /> <input type="text" name="username" value="Alice" onChange={[Function]} /> </section>
ã芧ã®ãšãããEnzymeã¯ã³ã³ããŒãã³ããã¬ã³ããªã³ã°ããŸããããåã蟌ã¿ã³ã³ããŒãã³ãã¯ã¬ã³ããªã³ã°ããŸããã§ããã ããã«ããããããã圌ã¯ãããã®æ£ãããã©ã¡ãŒã¿ãŒãäœæããå¿ èŠã«å¿ããŠãããããã§ãã¯ã§ããŸãã
ããã§ããµãŒãã§ã¹ã¬ã³ããªã³ã°ãé©åã§ã¯ãªããmountã¡ãœãããåŒã³åºããŠå®å šãªã¬ã³ããªã³ã°ã䜿çšããå¿ èŠãããå Žåã®ãªãã·ã§ã³ãèŠãŠã¿ãŸãããã 次ã®å ŽåããµãŒãã§ã¹ã¬ã³ããªã³ã°ã¯æ©èœããŸããã
- ã³ã³ããŒãã³ãã«ã¯ãcomponentWillMountãcomponentDidMountãªã©ã®ã©ã€ããµã€ã¯ã«ã¡ãœããã®ããžãã¯ãå«ãŸããŠããŸãã ãã¡ãããæåã§åŒã³åºãããšã¯ã§ããŸããããããåžžã«è¯ãæ¹æ³ã§ãããã©ããã¯ããããŸããã
- ã³ã³ããŒãã³ãã¯DOMãšå¯Ÿè©±ããå¿ èŠããããŸãã å®éã®ãšããããµãŒãã§ã¹ã¬ã³ããªã³ã°ã¯JSDOMã䜿çšãããDOMãšã®ãã¹ãŠã®å¯Ÿè©±ã¯åã«æ©èœããŸããã
- è€æ°ã®ã³ã³ããŒãã³ããçžäºã«çµ±åãããŠããããšã確èªããå¿ èŠããããŸããããšãã°ãToDoäœæã³ã³ããŒãã³ããšToDoãªã¹ãã³ã³ããŒãã³ãã1ããŒãžã§äœ¿çšããŸã
ãããã®å Žåãshallowã®ä»£ããã«mountã䜿çšããå¿ èŠããããŸããæ®å¿µãªãããDOMãšJsdomã©ã€ãã©ãªãããŒãããå¿ èŠãããããããã¹ããé ããªããŸãã ãããã£ãŠãå®å šãªã¬ã³ããªã³ã°ãå¿ èŠãªå Žåã®äŸã次ã«ç€ºããŸãã
import React, { PropTypes, Component } from 'react' import Wellcome from './Wellcome' class Home extends Component { componentWillMount() { this.props.fetchUsername(); } onChangeUsername(e) { this.props.changeUsername(e.target.value); } render() { return ( <section className='home'> <h1>Home</h1> <Wellcome username={this.props.username} /> <input type="text" name="username" value={this.props.username} onChange={this.onChangeUsername.bind(this)} /> </section> ); } } Home.propTypes = { changeUsername: PropTypes.func.isRequired, fetchUsername: PropTypes.func, }; export default Home;
ãããŠç§ãã¡ã®ãã¹ãïŒ
import React from 'react' import { mount } from 'enzyme' import Home from './Home' import Wellcome from './Wellcome'; describe('<Home />', () => { it('should fetch username on mount', () => { const fetchUsernameSpy = jest.fn(cb => 'Alie'); const renderedComponent = mount( <Home username={'Alie'} changeUsername={jest.fn()} /> ); // console.log(renderedComponent.debug()); expect(fetchUsernameSpy).toBeCalled(); }) })
ãããã°ã³ãŒã«ããè¿ããããã®ãèŠãŠã¿ãŸãããã
<Home username="Alise" changeUsername={[Function]} fetchUsername={[Function]}> <section className="home"> <h1> Home </h1> <Wellcome username="Alie"> <div> <span onClick={[Function]}> Wellcome Alise </span> </div> </Wellcome> <input type="text" name="username" value="Alie" onChange={[Function]} /> </section> </Home>
ã芧ã®ãšãããå®å šãªã¬ã³ããªã³ã°ã«ãããEnzymeã¯çµã¿èŸŒã¿ã³ã³ããŒãã³ããã¬ã³ããªã³ã°ããLifeCycleã³ã³ããŒãã³ãã®ãã¹ãŠã®ã¡ãœãããéå§ãããŸããã
Enzymeã«åãã£ãŠããæåŸã®ã¿ã€ãã®ã¬ã³ããªã³ã°ã§ããéçã¬ã³ããªã³ã°ã«ã€ããŠã¯ãæ€èšããå¿ èŠããããŸãã Cherioã©ã€ãã©ãªã䜿çšããŠã³ã³ããŒãã³ããHTMLæååã«ã¬ã³ããªã³ã°ããshallowã¡ãœããããã³mountã¡ãœããã«ãã£ãŠæå®ããããªããžã§ã¯ãã®ãããªãªããžã§ã¯ããè¿ããŸãã ãã®ãã¹ãã¯ãmountåŒã³åºããrenderã«çœ®ãæããã ãã§ãåã®ãã¹ããšã¯ç°ãªããããããããã°èªåã§æžãããšãã§ããŸãã
ã³ã³ããŒãã³ãã®HTMLæ§é ã®ã¿ãåæããå¿ èŠãããããã¹ãã®é床ãéèŠã§ããå Žåããã®ã¡ãœããã«ã¯1ã€ã®ã¢ããªã±ãŒã·ã§ã³ãã衚瀺ãããŸããã éçã¬ã³ããªã³ã°ã䜿çšãããšããã¹ãã¯å®å šã¬ã³ããªã³ã°ã䜿çšãããããéãå®è¡ãããŸãã ãã®ã¿ã€ãã®ã¬ã³ããªã³ã°ã®ä»ã®çšéã¯èŠã€ãããŸããã§ããã
äŒæ©
ãã®ããããã®èšäºã§ã¯ãReactã³ã³ããŒãã³ãã®ãã¹ãããã¹ãã«äœ¿çšããæ°ãããŠãŒãã£ãªãã£ããæãããããã¹ããäœæããããã®æ¢è£œã®ãåç©«æ©ããçµã¿ç«ãŠãŸããã ãã®ãããã¯ãèå³æ·±ãå Žåã次ã®èšäºã§ã¯ãreduxãredux-sagaãreact-intlãã¢ãŒãã«ãŠã£ã³ããŠãããã³ãã¹ããè€éã«ããä»ã®èŠçŽ ã䜿çšãããããè€éãªã¢ããªã±ãŒã·ã§ã³ãçå£ã«ãã¹ãããããšããŸãã
ããªãã«ãšã£ãŠã°ãªãŒã³ãã¹ããš100ïŒ ã®åæ Œç